mirror of
https://github.com/Next-Flip/Momentum-Firmware.git
synced 2026-07-23 01:18:12 -07:00
Merge branch 'dev' into zlo/allocator-playground
This commit is contained in:
@@ -380,8 +380,8 @@ static GapConfig template_config = {
|
||||
.pairing_method = GapPairingPinCodeVerifyYesNo,
|
||||
.conn_param =
|
||||
{
|
||||
.conn_int_min = 0x18, // 30 ms
|
||||
.conn_int_max = 0x24, // 45 ms
|
||||
.conn_int_min = 0x18, // AN5289: 4.7, we need at least 25ms + advertisement, which is 30 ms
|
||||
.conn_int_max = 0x18, // 30 ms
|
||||
.slave_latency = 0,
|
||||
.supervisor_timeout = 0,
|
||||
},
|
||||
|
||||
@@ -24,6 +24,7 @@ env.Append(
|
||||
File("protocols/mf_desfire/mf_desfire.h"),
|
||||
File("protocols/slix/slix.h"),
|
||||
File("protocols/st25tb/st25tb.h"),
|
||||
File("protocols/felica/felica.h"),
|
||||
# Pollers
|
||||
File("protocols/iso14443_3a/iso14443_3a_poller.h"),
|
||||
File("protocols/iso14443_3b/iso14443_3b_poller.h"),
|
||||
@@ -33,6 +34,7 @@ env.Append(
|
||||
File("protocols/mf_classic/mf_classic_poller.h"),
|
||||
File("protocols/mf_desfire/mf_desfire_poller.h"),
|
||||
File("protocols/st25tb/st25tb_poller.h"),
|
||||
File("protocols/felica/felica_poller.h"),
|
||||
# Listeners
|
||||
File("protocols/iso14443_3a/iso14443_3a_listener.h"),
|
||||
File("protocols/iso14443_4a/iso14443_4a_listener.h"),
|
||||
|
||||
@@ -13,6 +13,14 @@
|
||||
|
||||
static const uint32_t felica_data_format_version = 1;
|
||||
|
||||
/** @brief This is used in felica_prepare_first_block to define which
|
||||
* type of block needs to be prepared.
|
||||
*/
|
||||
typedef enum {
|
||||
FelicaMACTypeRead,
|
||||
FelicaMACTypeWrite,
|
||||
} FelicaMACType;
|
||||
|
||||
const NfcDeviceBase nfc_device_felica = {
|
||||
.protocol_name = FELICA_PROTOCOL_NAME,
|
||||
.alloc = (NfcDeviceAlloc)felica_alloc,
|
||||
@@ -35,18 +43,18 @@ FelicaData* felica_alloc(void) {
|
||||
}
|
||||
|
||||
void felica_free(FelicaData* data) {
|
||||
furi_assert(data);
|
||||
|
||||
furi_check(data);
|
||||
free(data);
|
||||
}
|
||||
|
||||
void felica_reset(FelicaData* data) {
|
||||
furi_check(data);
|
||||
memset(data, 0, sizeof(FelicaData));
|
||||
}
|
||||
|
||||
void felica_copy(FelicaData* data, const FelicaData* other) {
|
||||
furi_assert(data);
|
||||
furi_assert(other);
|
||||
furi_check(data);
|
||||
furi_check(other);
|
||||
|
||||
*data = *other;
|
||||
}
|
||||
@@ -59,7 +67,7 @@ bool felica_verify(FelicaData* data, const FuriString* device_type) {
|
||||
}
|
||||
|
||||
bool felica_load(FelicaData* data, FlipperFormat* ff, uint32_t version) {
|
||||
furi_assert(data);
|
||||
furi_check(data);
|
||||
|
||||
bool parsed = false;
|
||||
|
||||
@@ -77,13 +85,32 @@ bool felica_load(FelicaData* data, FlipperFormat* ff, uint32_t version) {
|
||||
break;
|
||||
|
||||
parsed = true;
|
||||
uint32_t blocks_total = 0;
|
||||
uint32_t blocks_read = 0;
|
||||
if(!flipper_format_read_uint32(ff, "Blocks total", &blocks_total, 1)) break;
|
||||
if(!flipper_format_read_uint32(ff, "Blocks read", &blocks_read, 1)) break;
|
||||
data->blocks_total = (uint8_t)blocks_total;
|
||||
data->blocks_read = (uint8_t)blocks_read;
|
||||
|
||||
FuriString* temp_str = furi_string_alloc();
|
||||
for(uint8_t i = 0; i < data->blocks_total; i++) {
|
||||
furi_string_printf(temp_str, "Block %d", i);
|
||||
if(!flipper_format_read_hex(
|
||||
ff,
|
||||
furi_string_get_cstr(temp_str),
|
||||
(&data->data.dump[i * sizeof(FelicaBlock)]),
|
||||
sizeof(FelicaBlock))) {
|
||||
parsed = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} while(false);
|
||||
|
||||
return parsed;
|
||||
}
|
||||
|
||||
bool felica_save(const FelicaData* data, FlipperFormat* ff) {
|
||||
furi_assert(data);
|
||||
furi_check(data);
|
||||
|
||||
bool saved = false;
|
||||
|
||||
@@ -98,15 +125,33 @@ bool felica_save(const FelicaData* data, FlipperFormat* ff) {
|
||||
ff, FELICA_MANUFACTURE_PARAMETER, data->pmm.data, FELICA_PMM_SIZE))
|
||||
break;
|
||||
|
||||
uint32_t blocks_total = data->blocks_total;
|
||||
uint32_t blocks_read = data->blocks_read;
|
||||
if(!flipper_format_write_uint32(ff, "Blocks total", &blocks_total, 1)) break;
|
||||
if(!flipper_format_write_uint32(ff, "Blocks read", &blocks_read, 1)) break;
|
||||
|
||||
saved = true;
|
||||
FuriString* temp_str = furi_string_alloc();
|
||||
for(uint8_t i = 0; i < blocks_total; i++) {
|
||||
furi_string_printf(temp_str, "Block %d", i);
|
||||
if(!flipper_format_write_hex(
|
||||
ff,
|
||||
furi_string_get_cstr(temp_str),
|
||||
(&data->data.dump[i * sizeof(FelicaBlock)]),
|
||||
sizeof(FelicaBlock))) {
|
||||
saved = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
furi_string_free(temp_str);
|
||||
} while(false);
|
||||
|
||||
return saved;
|
||||
}
|
||||
|
||||
bool felica_is_equal(const FelicaData* data, const FelicaData* other) {
|
||||
furi_assert(data);
|
||||
furi_assert(other);
|
||||
furi_check(data);
|
||||
furi_check(other);
|
||||
|
||||
return memcmp(data, other, sizeof(FelicaData)) == 0;
|
||||
}
|
||||
@@ -119,7 +164,7 @@ const char* felica_get_device_name(const FelicaData* data, NfcDeviceNameType nam
|
||||
}
|
||||
|
||||
const uint8_t* felica_get_uid(const FelicaData* data, size_t* uid_len) {
|
||||
furi_assert(data);
|
||||
furi_check(data);
|
||||
|
||||
// Consider Manufacturer ID as UID
|
||||
if(uid_len) {
|
||||
@@ -130,7 +175,7 @@ const uint8_t* felica_get_uid(const FelicaData* data, size_t* uid_len) {
|
||||
}
|
||||
|
||||
bool felica_set_uid(FelicaData* data, const uint8_t* uid, size_t uid_len) {
|
||||
furi_assert(data);
|
||||
furi_check(data);
|
||||
|
||||
// Consider Manufacturer ID as UID
|
||||
const bool uid_valid = uid_len == FELICA_IDM_SIZE;
|
||||
@@ -145,3 +190,149 @@ FelicaData* felica_get_base_data(const FelicaData* data) {
|
||||
UNUSED(data);
|
||||
furi_crash("No base data");
|
||||
}
|
||||
|
||||
static void felica_reverse_copy_block(const uint8_t* array, uint8_t* reverse_array) {
|
||||
furi_assert(array);
|
||||
furi_assert(reverse_array);
|
||||
|
||||
for(int i = 0; i < 8; i++) {
|
||||
reverse_array[i] = array[7 - i];
|
||||
}
|
||||
}
|
||||
|
||||
void felica_calculate_session_key(
|
||||
mbedtls_des3_context* ctx,
|
||||
const uint8_t* ck,
|
||||
const uint8_t* rc,
|
||||
uint8_t* out) {
|
||||
furi_check(ctx);
|
||||
furi_check(ck);
|
||||
furi_check(rc);
|
||||
furi_check(out);
|
||||
|
||||
uint8_t iv[8];
|
||||
memset(iv, 0, 8);
|
||||
|
||||
uint8_t ck_reversed[16];
|
||||
felica_reverse_copy_block(ck, ck_reversed);
|
||||
felica_reverse_copy_block(ck + 8, ck_reversed + 8);
|
||||
|
||||
uint8_t rc_reversed[16];
|
||||
felica_reverse_copy_block(rc, rc_reversed);
|
||||
felica_reverse_copy_block(rc + 8, rc_reversed + 8);
|
||||
|
||||
mbedtls_des3_set2key_enc(ctx, ck_reversed);
|
||||
mbedtls_des3_crypt_cbc(ctx, MBEDTLS_DES_ENCRYPT, FELICA_DATA_BLOCK_SIZE, iv, rc_reversed, out);
|
||||
}
|
||||
|
||||
static bool felica_calculate_mac(
|
||||
mbedtls_des3_context* ctx,
|
||||
const uint8_t* session_key,
|
||||
const uint8_t* rc,
|
||||
const uint8_t* first_block,
|
||||
const uint8_t* data,
|
||||
const size_t length,
|
||||
uint8_t* mac) {
|
||||
furi_check((length % 8) == 0);
|
||||
|
||||
uint8_t reverse_data[8];
|
||||
uint8_t iv[8];
|
||||
uint8_t out[8];
|
||||
mbedtls_des3_set2key_enc(ctx, session_key);
|
||||
|
||||
felica_reverse_copy_block(rc, iv);
|
||||
felica_reverse_copy_block(first_block, reverse_data);
|
||||
uint8_t i = 0;
|
||||
bool error = false;
|
||||
do {
|
||||
if(mbedtls_des3_crypt_cbc(ctx, MBEDTLS_DES_ENCRYPT, 8, iv, reverse_data, out) == 0) {
|
||||
memcpy(iv, out, sizeof(iv));
|
||||
felica_reverse_copy_block(data + i, reverse_data);
|
||||
i += 8;
|
||||
} else {
|
||||
error = true;
|
||||
break;
|
||||
}
|
||||
} while(i <= length);
|
||||
|
||||
if(!error) {
|
||||
felica_reverse_copy_block(out, mac);
|
||||
}
|
||||
return !error;
|
||||
}
|
||||
|
||||
static void felica_prepare_first_block(
|
||||
FelicaMACType operation_type,
|
||||
const uint8_t* blocks,
|
||||
const uint8_t block_count,
|
||||
uint8_t* out) {
|
||||
furi_check(blocks);
|
||||
furi_check(out);
|
||||
if(operation_type == FelicaMACTypeRead) {
|
||||
memset(out, 0xFF, 8);
|
||||
for(uint8_t i = 0, j = 0; i < block_count; i++, j += 2) {
|
||||
out[j] = blocks[i];
|
||||
out[j + 1] = 0;
|
||||
}
|
||||
} else {
|
||||
furi_check(block_count == 4);
|
||||
memset(out, 0, 8);
|
||||
out[0] = blocks[0];
|
||||
out[1] = blocks[1];
|
||||
out[2] = blocks[2];
|
||||
out[4] = blocks[3];
|
||||
out[6] = FELICA_BLOCK_INDEX_MAC_A;
|
||||
}
|
||||
}
|
||||
|
||||
bool felica_check_mac(
|
||||
mbedtls_des3_context* ctx,
|
||||
const uint8_t* session_key,
|
||||
const uint8_t* rc,
|
||||
const uint8_t* blocks,
|
||||
const uint8_t block_count,
|
||||
uint8_t* data) {
|
||||
furi_check(ctx);
|
||||
furi_check(session_key);
|
||||
furi_check(rc);
|
||||
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);
|
||||
|
||||
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(
|
||||
mbedtls_des3_context* ctx,
|
||||
const uint8_t* session_key,
|
||||
const uint8_t* rc,
|
||||
const uint8_t* wcnt,
|
||||
const uint8_t* data,
|
||||
uint8_t* mac) {
|
||||
furi_check(ctx);
|
||||
furi_check(session_key);
|
||||
furi_check(rc);
|
||||
furi_check(wcnt);
|
||||
furi_check(data);
|
||||
furi_check(mac);
|
||||
|
||||
const uint8_t WCNT_length = 3;
|
||||
uint8_t block_data[WCNT_length + 1];
|
||||
uint8_t first_block[8];
|
||||
|
||||
memcpy(block_data, wcnt, WCNT_length);
|
||||
block_data[3] = FELICA_BLOCK_INDEX_STATE;
|
||||
felica_prepare_first_block(FelicaMACTypeWrite, block_data, WCNT_length + 1, first_block);
|
||||
|
||||
uint8_t session_swapped[FELICA_DATA_BLOCK_SIZE];
|
||||
memcpy(session_swapped, session_key + 8, 8);
|
||||
memcpy(session_swapped + 8, session_key, 8);
|
||||
felica_calculate_mac(ctx, session_swapped, rc, first_block, data, FELICA_DATA_BLOCK_SIZE, mac);
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <toolbox/bit_buffer.h>
|
||||
#include <nfc/protocols/nfc_device_base_i.h>
|
||||
#include <mbedtls/include/mbedtls/des.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
@@ -9,6 +10,23 @@ extern "C" {
|
||||
|
||||
#define FELICA_IDM_SIZE (8U)
|
||||
#define FELICA_PMM_SIZE (8U)
|
||||
#define FELICA_DATA_BLOCK_SIZE (16U)
|
||||
|
||||
#define FELICA_BLOCKS_TOTAL_COUNT (27U)
|
||||
#define FELICA_BLOCK_INDEX_REG (0x0EU)
|
||||
#define FELICA_BLOCK_INDEX_RC (0x80U)
|
||||
#define FELICA_BLOCK_INDEX_MAC (0x81U)
|
||||
#define FELICA_BLOCK_INDEX_ID (0x82U)
|
||||
#define FELICA_BLOCK_INDEX_D_ID (0x83U)
|
||||
#define FELICA_BLOCK_INDEX_SER_C (0x84U)
|
||||
#define FELICA_BLOCK_INDEX_SYS_C (0x85U)
|
||||
#define FELICA_BLOCK_INDEX_CKV (0x86U)
|
||||
#define FELICA_BLOCK_INDEX_CK (0x87U)
|
||||
#define FELICA_BLOCK_INDEX_MC (0x88U)
|
||||
#define FELICA_BLOCK_INDEX_WCNT (0x90U)
|
||||
#define FELICA_BLOCK_INDEX_MAC_A (0x91U)
|
||||
#define FELICA_BLOCK_INDEX_STATE (0x92U)
|
||||
#define FELICA_BLOCK_INDEX_CRC_CHECK (0xA0U)
|
||||
|
||||
#define FELICA_GUARD_TIME_US (20000U)
|
||||
#define FELICA_FDT_POLL_FC (10000U)
|
||||
@@ -23,6 +41,7 @@ extern "C" {
|
||||
#define FELICA_TIME_SLOT_8 (0x07U)
|
||||
#define FELICA_TIME_SLOT_16 (0x0FU)
|
||||
|
||||
/** @brief Type of possible Felica errors */
|
||||
typedef enum {
|
||||
FelicaErrorNone,
|
||||
FelicaErrorNotPresent,
|
||||
@@ -35,17 +54,78 @@ typedef enum {
|
||||
FelicaErrorTimeout,
|
||||
} FelicaError;
|
||||
|
||||
/** @brief Separate type for card key block. Used in authentication process */
|
||||
typedef struct {
|
||||
uint8_t data[FELICA_DATA_BLOCK_SIZE];
|
||||
} FelicaCardKey;
|
||||
|
||||
/** @brief In Felica there two types of auth. Internal is the first one, after
|
||||
* which external became possible. Here are two flags representing which one
|
||||
* was passed */
|
||||
typedef struct {
|
||||
bool internal : 1;
|
||||
bool external : 1;
|
||||
} FelicaAuthenticationStatus;
|
||||
|
||||
/** @brief Struct which controls the process of authentication and can be passed as
|
||||
* a parameter to the application level. In order to force user to fill card key block data. */
|
||||
typedef struct {
|
||||
bool skip_auth; /**< By default it is true, so auth is skipped. By setting this to false several auth steps will be performed in order to pass auth*/
|
||||
FelicaCardKey
|
||||
card_key; /**< User must fill this field with known card key in order to pass auth*/
|
||||
FelicaAuthenticationStatus auth_status; /**< Authentication status*/
|
||||
} FelicaAuthenticationContext;
|
||||
|
||||
/** @brief Felica ID block */
|
||||
typedef struct {
|
||||
uint8_t data[FELICA_IDM_SIZE];
|
||||
} FelicaIDm;
|
||||
|
||||
/** @brief Felica PMm block */
|
||||
typedef struct {
|
||||
uint8_t data[FELICA_PMM_SIZE];
|
||||
} FelicaPMm;
|
||||
|
||||
/** @brief Felica block with status flags indicating last operation with it.
|
||||
* See Felica manual for more details on status codes. */
|
||||
typedef struct {
|
||||
uint8_t SF1; /**< Status flag 1, equals to 0 when success*/
|
||||
uint8_t SF2; /**< Status flag 2, equals to 0 when success*/
|
||||
uint8_t data[FELICA_DATA_BLOCK_SIZE]; /**< Block data */
|
||||
} FelicaBlock;
|
||||
|
||||
/** @brief Felica filesystem structure */
|
||||
typedef struct {
|
||||
FelicaBlock spad[14];
|
||||
FelicaBlock reg;
|
||||
FelicaBlock rc;
|
||||
FelicaBlock mac;
|
||||
FelicaBlock id;
|
||||
FelicaBlock d_id;
|
||||
FelicaBlock ser_c;
|
||||
FelicaBlock sys_c;
|
||||
FelicaBlock ckv;
|
||||
FelicaBlock ck;
|
||||
FelicaBlock mc;
|
||||
FelicaBlock wcnt;
|
||||
FelicaBlock mac_a;
|
||||
FelicaBlock state;
|
||||
FelicaBlock crc_check;
|
||||
} FelicaFileSystem;
|
||||
|
||||
/** @brief Union which represents filesystem in junction with plain data dump */
|
||||
typedef union {
|
||||
FelicaFileSystem fs;
|
||||
uint8_t dump[sizeof(FelicaFileSystem)];
|
||||
} FelicaFSUnion;
|
||||
|
||||
/** @brief Structure used to store Felica data and additional values about reading */
|
||||
typedef struct {
|
||||
FelicaIDm idm;
|
||||
FelicaPMm pmm;
|
||||
uint8_t blocks_total;
|
||||
uint8_t blocks_read;
|
||||
FelicaFSUnion data;
|
||||
} FelicaData;
|
||||
|
||||
extern const NfcDeviceBase nfc_device_felica;
|
||||
@@ -74,6 +154,27 @@ bool felica_set_uid(FelicaData* data, const uint8_t* uid, size_t uid_len);
|
||||
|
||||
FelicaData* felica_get_base_data(const FelicaData* data);
|
||||
|
||||
void felica_calculate_session_key(
|
||||
mbedtls_des3_context* ctx,
|
||||
const uint8_t* ck,
|
||||
const uint8_t* rc,
|
||||
uint8_t* out);
|
||||
|
||||
bool felica_check_mac(
|
||||
mbedtls_des3_context* ctx,
|
||||
const uint8_t* session_key,
|
||||
const uint8_t* rc,
|
||||
const uint8_t* blocks,
|
||||
const uint8_t block_count,
|
||||
uint8_t* data);
|
||||
|
||||
void felica_calculate_mac_write(
|
||||
mbedtls_des3_context* ctx,
|
||||
const uint8_t* session_key,
|
||||
const uint8_t* rc,
|
||||
const uint8_t* wcnt,
|
||||
const uint8_t* data,
|
||||
uint8_t* mac);
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -3,6 +3,11 @@
|
||||
#include <nfc/protocols/nfc_poller_base.h>
|
||||
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
|
||||
#define TAG "FelicaPoller"
|
||||
|
||||
typedef NfcCommand (*FelicaPollerReadHandler)(FelicaPoller* instance);
|
||||
|
||||
const FelicaData* felica_poller_get_data(FelicaPoller* instance) {
|
||||
furi_assert(instance);
|
||||
@@ -23,6 +28,8 @@ static FelicaPoller* felica_poller_alloc(Nfc* nfc) {
|
||||
nfc_set_guard_time_us(instance->nfc, FELICA_GUARD_TIME_US);
|
||||
nfc_set_fdt_poll_fc(instance->nfc, FELICA_FDT_POLL_FC);
|
||||
nfc_set_fdt_poll_poll_us(instance->nfc, FELICA_POLL_POLL_MIN_US);
|
||||
|
||||
mbedtls_des3_init(&instance->auth.des_context);
|
||||
instance->data = felica_alloc();
|
||||
|
||||
instance->felica_event.data = &instance->felica_event_data;
|
||||
@@ -40,6 +47,7 @@ static void felica_poller_free(FelicaPoller* instance) {
|
||||
furi_assert(instance->rx_buffer);
|
||||
furi_assert(instance->data);
|
||||
|
||||
mbedtls_des3_free(&instance->auth.des_context);
|
||||
bit_buffer_free(instance->tx_buffer);
|
||||
bit_buffer_free(instance->rx_buffer);
|
||||
felica_free(instance->data);
|
||||
@@ -55,6 +63,212 @@ static void
|
||||
instance->context = context;
|
||||
}
|
||||
|
||||
NfcCommand felica_poller_state_handler_idle(FelicaPoller* instance) {
|
||||
FURI_LOG_D(TAG, "Idle");
|
||||
felica_reset(instance->data);
|
||||
instance->state = FelicaPollerStateActivated;
|
||||
|
||||
return NfcCommandContinue;
|
||||
}
|
||||
|
||||
NfcCommand felica_poller_state_handler_activate(FelicaPoller* instance) {
|
||||
FURI_LOG_D(TAG, "Activate");
|
||||
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
|
||||
FelicaError error = felica_poller_activate(instance, instance->data);
|
||||
if(error == FelicaErrorNone) {
|
||||
furi_hal_random_fill_buf(instance->data->data.fs.rc.data, FELICA_DATA_BLOCK_SIZE);
|
||||
|
||||
instance->felica_event.type = FelicaPollerEventTypeRequestAuthContext;
|
||||
instance->felica_event_data.auth_context = &instance->auth.context;
|
||||
|
||||
instance->callback(instance->general_event, instance->context);
|
||||
|
||||
bool skip_auth = instance->auth.context.skip_auth;
|
||||
instance->state = skip_auth ? FelicaPollerStateReadBlocks :
|
||||
FelicaPollerStateAuthenticateInternal;
|
||||
} else if(error != FelicaErrorTimeout) {
|
||||
instance->felica_event.type = FelicaPollerEventTypeError;
|
||||
instance->felica_event_data.error = error;
|
||||
instance->state = FelicaPollerStateReadFailed;
|
||||
}
|
||||
return command;
|
||||
}
|
||||
|
||||
NfcCommand felica_poller_state_handler_auth_internal(FelicaPoller* instance) {
|
||||
FURI_LOG_D(TAG, "Auth Internal");
|
||||
|
||||
felica_calculate_session_key(
|
||||
&instance->auth.des_context,
|
||||
instance->auth.context.card_key.data,
|
||||
instance->data->data.fs.rc.data,
|
||||
instance->auth.session_key.data);
|
||||
|
||||
instance->state = FelicaPollerStateReadBlocks;
|
||||
|
||||
uint8_t blocks[3] = {FELICA_BLOCK_INDEX_RC, 0, 0};
|
||||
FelicaPollerWriteCommandResponse* tx_resp;
|
||||
do {
|
||||
FelicaError error = felica_poller_write_blocks(
|
||||
instance, 1, blocks, instance->data->data.fs.rc.data, &tx_resp);
|
||||
if((error != FelicaErrorNone) || (tx_resp->SF1 != 0) || (tx_resp->SF2 != 0)) break;
|
||||
|
||||
blocks[0] = FELICA_BLOCK_INDEX_ID;
|
||||
blocks[1] = FELICA_BLOCK_INDEX_WCNT;
|
||||
blocks[2] = FELICA_BLOCK_INDEX_MAC_A;
|
||||
FelicaPollerReadCommandResponse* rx_resp;
|
||||
error = felica_poller_read_blocks(instance, sizeof(blocks), blocks, &rx_resp);
|
||||
if(error != FelicaErrorNone || rx_resp->SF1 != 0 || rx_resp->SF2 != 0) break;
|
||||
|
||||
if(felica_check_mac(
|
||||
&instance->auth.des_context,
|
||||
instance->auth.session_key.data,
|
||||
instance->data->data.fs.rc.data,
|
||||
blocks,
|
||||
rx_resp->block_count,
|
||||
rx_resp->data)) {
|
||||
instance->auth.context.auth_status.internal = true;
|
||||
instance->data->data.fs.wcnt.SF1 = 0;
|
||||
instance->data->data.fs.wcnt.SF2 = 0;
|
||||
memcpy(
|
||||
instance->data->data.fs.wcnt.data,
|
||||
rx_resp->data + FELICA_DATA_BLOCK_SIZE,
|
||||
FELICA_DATA_BLOCK_SIZE);
|
||||
instance->state = FelicaPollerStateAuthenticateExternal;
|
||||
}
|
||||
} while(false);
|
||||
|
||||
return NfcCommandContinue;
|
||||
}
|
||||
|
||||
NfcCommand felica_poller_state_handler_auth_external(FelicaPoller* instance) {
|
||||
FURI_LOG_D(TAG, "Auth External");
|
||||
instance->state = FelicaPollerStateReadBlocks;
|
||||
uint8_t blocks[2];
|
||||
|
||||
instance->data->data.fs.state.data[0] = 1;
|
||||
FelicaAuthentication* auth = &instance->auth;
|
||||
felica_calculate_mac_write(
|
||||
&auth->des_context,
|
||||
auth->session_key.data,
|
||||
instance->data->data.fs.rc.data,
|
||||
instance->data->data.fs.wcnt.data,
|
||||
instance->data->data.fs.state.data,
|
||||
instance->data->data.fs.mac_a.data);
|
||||
|
||||
memcpy(instance->data->data.fs.mac_a.data + 8, instance->data->data.fs.wcnt.data, 3); //-V1086
|
||||
|
||||
uint8_t tx_data[FELICA_DATA_BLOCK_SIZE * 2];
|
||||
memcpy(tx_data, instance->data->data.fs.state.data, FELICA_DATA_BLOCK_SIZE);
|
||||
memcpy(
|
||||
tx_data + FELICA_DATA_BLOCK_SIZE,
|
||||
instance->data->data.fs.mac_a.data,
|
||||
FELICA_DATA_BLOCK_SIZE);
|
||||
|
||||
do {
|
||||
blocks[0] = FELICA_BLOCK_INDEX_STATE;
|
||||
blocks[1] = FELICA_BLOCK_INDEX_MAC_A;
|
||||
FelicaPollerWriteCommandResponse* tx_resp;
|
||||
FelicaError error = felica_poller_write_blocks(instance, 2, blocks, tx_data, &tx_resp);
|
||||
if(error != FelicaErrorNone || tx_resp->SF1 != 0 || tx_resp->SF2 != 0) break;
|
||||
|
||||
FelicaPollerReadCommandResponse* rx_resp;
|
||||
error = felica_poller_read_blocks(instance, 1, blocks, &rx_resp);
|
||||
if(error != FelicaErrorNone || rx_resp->SF1 != 0 || rx_resp->SF2 != 0) break;
|
||||
|
||||
instance->data->data.fs.state.SF1 = 0;
|
||||
instance->data->data.fs.state.SF2 = 0;
|
||||
memcpy(instance->data->data.fs.state.data, rx_resp->data, FELICA_DATA_BLOCK_SIZE);
|
||||
instance->auth.context.auth_status.external = instance->data->data.fs.state.data[0];
|
||||
} while(false);
|
||||
instance->state = FelicaPollerStateReadBlocks;
|
||||
return NfcCommandContinue;
|
||||
}
|
||||
|
||||
NfcCommand felica_poller_state_handler_read_blocks(FelicaPoller* instance) {
|
||||
FURI_LOG_D(TAG, "Read Blocks");
|
||||
|
||||
uint8_t block_count = 1;
|
||||
uint8_t block_list[4] = {0, 0, 0, 0};
|
||||
block_list[0] = instance->block_index;
|
||||
|
||||
instance->block_index++;
|
||||
if(instance->block_index == FELICA_BLOCK_INDEX_REG + 1) {
|
||||
instance->block_index = FELICA_BLOCK_INDEX_RC;
|
||||
} else if(instance->block_index == FELICA_BLOCK_INDEX_MC + 1) {
|
||||
instance->block_index = FELICA_BLOCK_INDEX_WCNT;
|
||||
} else if(instance->block_index == FELICA_BLOCK_INDEX_STATE + 1) {
|
||||
instance->block_index = FELICA_BLOCK_INDEX_CRC_CHECK;
|
||||
}
|
||||
|
||||
FelicaPollerReadCommandResponse* response;
|
||||
FelicaError error = felica_poller_read_blocks(instance, block_count, block_list, &response);
|
||||
if(error == FelicaErrorNone) {
|
||||
block_count = (response->SF1 == 0) ? response->block_count : block_count;
|
||||
uint8_t* data_ptr =
|
||||
instance->data->data.dump + instance->data->blocks_total * sizeof(FelicaBlock);
|
||||
|
||||
*data_ptr++ = response->SF1;
|
||||
*data_ptr++ = response->SF2;
|
||||
|
||||
if(response->SF1 == 0) {
|
||||
uint8_t* response_data_ptr = response->data;
|
||||
instance->data->blocks_read++;
|
||||
memcpy(data_ptr, response_data_ptr, FELICA_DATA_BLOCK_SIZE);
|
||||
} else {
|
||||
memset(data_ptr, 0, FELICA_DATA_BLOCK_SIZE);
|
||||
}
|
||||
instance->data->blocks_total++;
|
||||
|
||||
if(instance->data->blocks_total == FELICA_BLOCKS_TOTAL_COUNT) {
|
||||
instance->state = FelicaPollerStateReadSuccess;
|
||||
}
|
||||
} else {
|
||||
instance->felica_event.type = FelicaPollerEventTypeError;
|
||||
instance->felica_event_data.error = error;
|
||||
instance->state = FelicaPollerStateReadFailed;
|
||||
}
|
||||
|
||||
return NfcCommandContinue;
|
||||
}
|
||||
|
||||
NfcCommand felica_poller_state_handler_read_success(FelicaPoller* instance) {
|
||||
FURI_LOG_D(TAG, "Read Success");
|
||||
|
||||
if(!instance->auth.context.auth_status.internal ||
|
||||
!instance->auth.context.auth_status.external) {
|
||||
instance->data->blocks_read--;
|
||||
instance->felica_event.type = FelicaPollerEventTypeIncomplete;
|
||||
} else {
|
||||
memcpy(
|
||||
instance->data->data.fs.ck.data,
|
||||
instance->auth.context.card_key.data,
|
||||
FELICA_DATA_BLOCK_SIZE);
|
||||
instance->felica_event.type = FelicaPollerEventTypeReady;
|
||||
}
|
||||
|
||||
instance->felica_event_data.error = FelicaErrorNone;
|
||||
return instance->callback(instance->general_event, instance->context);
|
||||
}
|
||||
|
||||
NfcCommand felica_poller_state_handler_read_failed(FelicaPoller* instance) {
|
||||
FURI_LOG_D(TAG, "Read Fail");
|
||||
instance->callback(instance->general_event, instance->context);
|
||||
|
||||
return NfcCommandStop;
|
||||
}
|
||||
|
||||
static const FelicaPollerReadHandler felica_poller_handler[FelicaPollerStateNum] = {
|
||||
[FelicaPollerStateIdle] = felica_poller_state_handler_idle,
|
||||
[FelicaPollerStateActivated] = felica_poller_state_handler_activate,
|
||||
[FelicaPollerStateAuthenticateInternal] = felica_poller_state_handler_auth_internal,
|
||||
[FelicaPollerStateAuthenticateExternal] = felica_poller_state_handler_auth_external,
|
||||
[FelicaPollerStateReadBlocks] = felica_poller_state_handler_read_blocks,
|
||||
[FelicaPollerStateReadSuccess] = felica_poller_state_handler_read_success,
|
||||
[FelicaPollerStateReadFailed] = felica_poller_state_handler_read_failed,
|
||||
};
|
||||
|
||||
static NfcCommand felica_poller_run(NfcGenericEvent event, void* context) {
|
||||
furi_assert(context);
|
||||
furi_assert(event.protocol == NfcProtocolInvalid);
|
||||
@@ -65,24 +279,7 @@ static NfcCommand felica_poller_run(NfcGenericEvent event, void* context) {
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
|
||||
if(nfc_event->type == NfcEventTypePollerReady) {
|
||||
if(instance->state != FelicaPollerStateActivated) {
|
||||
FelicaError error = felica_poller_activate(instance, instance->data);
|
||||
if(error == FelicaErrorNone) {
|
||||
instance->felica_event.type = FelicaPollerEventTypeReady;
|
||||
instance->felica_event_data.error = error;
|
||||
command = instance->callback(instance->general_event, instance->context);
|
||||
} else {
|
||||
instance->felica_event.type = FelicaPollerEventTypeError;
|
||||
instance->felica_event_data.error = error;
|
||||
command = instance->callback(instance->general_event, instance->context);
|
||||
// Add delay to switch context
|
||||
furi_delay_ms(100);
|
||||
}
|
||||
} else {
|
||||
instance->felica_event.type = FelicaPollerEventTypeReady;
|
||||
instance->felica_event_data.error = FelicaErrorNone;
|
||||
command = instance->callback(instance->general_event, instance->context);
|
||||
}
|
||||
command = felica_poller_handler[instance->state](instance);
|
||||
}
|
||||
|
||||
return command;
|
||||
|
||||
@@ -19,14 +19,33 @@ typedef struct FelicaPoller FelicaPoller;
|
||||
*/
|
||||
typedef enum {
|
||||
FelicaPollerEventTypeError, /**< An error occured during activation procedure. */
|
||||
FelicaPollerEventTypeReady, /**< The card was activated by the poller. */
|
||||
FelicaPollerEventTypeReady, /**< The card was activated and fully read by the poller. */
|
||||
FelicaPollerEventTypeIncomplete, /**< The card was activated and partly read by the poller. */
|
||||
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.
|
||||
*/
|
||||
typedef union {
|
||||
FelicaError error; /**< Error code indicating card activation fail reason. */
|
||||
FelicaAuthenticationContext* auth_context; /**< Authentication context to be filled by user. */
|
||||
} FelicaPollerEventData;
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,6 +3,11 @@
|
||||
#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) {
|
||||
@@ -15,8 +20,8 @@ static FelicaError felica_poller_process_error(NfcError error) {
|
||||
}
|
||||
}
|
||||
|
||||
static FelicaError felica_poller_frame_exchange(
|
||||
FelicaPoller* instance,
|
||||
FelicaError felica_poller_frame_exchange(
|
||||
const FelicaPoller* instance,
|
||||
const BitBuffer* tx_buffer,
|
||||
BitBuffer* rx_buffer,
|
||||
uint32_t fwt) {
|
||||
@@ -93,11 +98,105 @@ FelicaError felica_poller_polling(
|
||||
return error;
|
||||
}
|
||||
|
||||
static void felica_poller_prepare_tx_buffer(
|
||||
const FelicaPoller* instance,
|
||||
const uint8_t command,
|
||||
const uint16_t service_code,
|
||||
const uint8_t block_count,
|
||||
const uint8_t* const blocks,
|
||||
const uint8_t data_block_count,
|
||||
const uint8_t* data) {
|
||||
FelicaCommandHeader cmd = {
|
||||
.code = command,
|
||||
.idm = instance->data->idm,
|
||||
.service_num = 1,
|
||||
.service_code = service_code,
|
||||
.block_count = block_count,
|
||||
};
|
||||
|
||||
FelicaBlockListElement block_list[4] = {{0}, {0}, {0}, {0}};
|
||||
for(uint8_t i = 0; i < block_count; i++) {
|
||||
block_list[i].length = 1;
|
||||
block_list[i].block_number = blocks[i];
|
||||
}
|
||||
|
||||
uint8_t block_list_count = block_count;
|
||||
uint8_t block_list_size = block_list_count * sizeof(FelicaBlockListElement);
|
||||
uint8_t total_size = sizeof(FelicaCommandHeader) + 1 + block_list_size +
|
||||
data_block_count * FELICA_DATA_BLOCK_SIZE;
|
||||
bit_buffer_reset(instance->tx_buffer);
|
||||
bit_buffer_append_byte(instance->tx_buffer, total_size);
|
||||
bit_buffer_append_bytes(instance->tx_buffer, (uint8_t*)&cmd, sizeof(FelicaCommandHeader));
|
||||
bit_buffer_append_bytes(instance->tx_buffer, (uint8_t*)&block_list, block_list_size);
|
||||
|
||||
if(data_block_count != 0) {
|
||||
bit_buffer_append_bytes(
|
||||
instance->tx_buffer, data, data_block_count * FELICA_DATA_BLOCK_SIZE);
|
||||
}
|
||||
}
|
||||
|
||||
FelicaError felica_poller_read_blocks(
|
||||
FelicaPoller* instance,
|
||||
const uint8_t block_count,
|
||||
const uint8_t* const block_numbers,
|
||||
FelicaPollerReadCommandResponse** const response_ptr) {
|
||||
furi_assert(instance);
|
||||
furi_assert(block_count <= 4);
|
||||
furi_assert(block_numbers);
|
||||
furi_assert(response_ptr);
|
||||
|
||||
felica_poller_prepare_tx_buffer(
|
||||
instance,
|
||||
FELICA_CMD_READ_WITHOUT_ENCRYPTION,
|
||||
FELICA_SERVICE_RO_ACCESS,
|
||||
block_count,
|
||||
block_numbers,
|
||||
0,
|
||||
NULL);
|
||||
bit_buffer_reset(instance->rx_buffer);
|
||||
|
||||
FelicaError error = felica_poller_frame_exchange(
|
||||
instance, instance->tx_buffer, instance->rx_buffer, FELICA_POLLER_POLLING_FWT);
|
||||
if(error == FelicaErrorNone) {
|
||||
*response_ptr = (FelicaPollerReadCommandResponse*)bit_buffer_get_data(instance->rx_buffer);
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
FelicaError felica_poller_write_blocks(
|
||||
const FelicaPoller* instance,
|
||||
const uint8_t block_count,
|
||||
const uint8_t* const block_numbers,
|
||||
const uint8_t* data,
|
||||
FelicaPollerWriteCommandResponse** const response_ptr) {
|
||||
furi_assert(instance);
|
||||
furi_assert(block_count <= 2);
|
||||
furi_assert(block_numbers);
|
||||
furi_assert(data);
|
||||
furi_assert(response_ptr);
|
||||
|
||||
felica_poller_prepare_tx_buffer(
|
||||
instance,
|
||||
FELICA_CMD_WRITE_WITHOUT_ENCRYPTION,
|
||||
FELICA_SERVICE_RW_ACCESS,
|
||||
block_count,
|
||||
block_numbers,
|
||||
block_count,
|
||||
data);
|
||||
bit_buffer_reset(instance->rx_buffer);
|
||||
|
||||
FelicaError error = felica_poller_frame_exchange(
|
||||
instance, instance->tx_buffer, instance->rx_buffer, FELICA_POLLER_POLLING_FWT);
|
||||
if(error == FelicaErrorNone) {
|
||||
*response_ptr =
|
||||
(FelicaPollerWriteCommandResponse*)bit_buffer_get_data(instance->rx_buffer);
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
FelicaError felica_poller_activate(FelicaPoller* instance, FelicaData* data) {
|
||||
furi_assert(instance);
|
||||
|
||||
felica_reset(data);
|
||||
|
||||
FelicaError ret;
|
||||
|
||||
do {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "felica_poller.h"
|
||||
|
||||
#include <toolbox/bit_buffer.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
@@ -18,11 +17,20 @@ extern "C" {
|
||||
typedef enum {
|
||||
FelicaPollerStateIdle,
|
||||
FelicaPollerStateActivated,
|
||||
FelicaPollerStateAuthenticateInternal,
|
||||
FelicaPollerStateAuthenticateExternal,
|
||||
FelicaPollerStateReadBlocks,
|
||||
FelicaPollerStateReadSuccess,
|
||||
FelicaPollerStateReadFailed,
|
||||
|
||||
FelicaPollerStateNum
|
||||
} FelicaPollerState;
|
||||
|
||||
struct FelicaPoller {
|
||||
Nfc* nfc;
|
||||
FelicaPollerState state;
|
||||
FelicaAuthentication auth;
|
||||
|
||||
FelicaData* data;
|
||||
BitBuffer* tx_buffer;
|
||||
BitBuffer* rx_buffer;
|
||||
@@ -31,6 +39,7 @@ struct FelicaPoller {
|
||||
FelicaPollerEvent felica_event;
|
||||
FelicaPollerEventData felica_event_data;
|
||||
NfcGenericCallback callback;
|
||||
uint8_t block_index;
|
||||
void* context;
|
||||
};
|
||||
|
||||
@@ -46,13 +55,106 @@ 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);
|
||||
|
||||
/**
|
||||
* @brief Performs felica polling operation as part of the activation process
|
||||
*
|
||||
* @param[in, out] instance pointer to the instance to be used in the transaction.
|
||||
* @param[in] cmd Pointer to polling command structure
|
||||
* @param[out] resp Pointer to the response structure
|
||||
* @return FelicaErrorNone on success, an error code on failure
|
||||
*/
|
||||
FelicaError felica_poller_polling(
|
||||
FelicaPoller* instance,
|
||||
const FelicaPollerPollingCommand* cmd,
|
||||
FelicaPollerPollingResponse* resp);
|
||||
|
||||
/**
|
||||
* @brief Performs felica read operation for blocks provided as parameters
|
||||
*
|
||||
* @param[in, out] instance pointer to the instance to be used in the transaction.
|
||||
* @param[in] block_count Amount of blocks involved in reading procedure
|
||||
* @param[in] block_numbers Array with block indexes according to felica docs
|
||||
* @param[out] response_ptr Pointer to the response structure
|
||||
* @return FelicaErrorNone on success, an error code on failure.
|
||||
*/
|
||||
FelicaError felica_poller_read_blocks(
|
||||
FelicaPoller* instance,
|
||||
const uint8_t block_count,
|
||||
const uint8_t* const block_numbers,
|
||||
FelicaPollerReadCommandResponse** const response_ptr);
|
||||
|
||||
/**
|
||||
* @brief Performs felica write operation with data provided as parameters
|
||||
*
|
||||
* @param[in, out] instance pointer to the instance to be used in the transaction.
|
||||
* @param[in] block_count Amount of blocks involved in writing procedure
|
||||
* @param[in] block_numbers Array with block indexes according to felica docs
|
||||
* @param[in] data Data of blocks provided in block_numbers
|
||||
* @param[out] response_ptr Pointer to the response structure
|
||||
* @return FelicaErrorNone on success, an error code on failure.
|
||||
*/
|
||||
FelicaError felica_poller_write_blocks(
|
||||
const FelicaPoller* instance,
|
||||
const uint8_t block_count,
|
||||
const uint8_t* const block_numbers,
|
||||
const uint8_t* data,
|
||||
FelicaPollerWriteCommandResponse** const response_ptr);
|
||||
|
||||
/**
|
||||
* @brief Perform frame exchange procedure.
|
||||
*
|
||||
* Prepares data for sending by adding crc, after that performs
|
||||
* low level calls to send package data to the card
|
||||
*
|
||||
* @param[in, out] instance pointer to the instance to be used in the transaction.
|
||||
* @param[in] tx_buffer pointer to the buffer with data to be transmitted
|
||||
* @param[out] rx_buffer pointer to the buffer with received data from card
|
||||
* @param[in] fwt timeout window
|
||||
* @return FelicaErrorNone on success, an error code on failure.
|
||||
*/
|
||||
FelicaError felica_poller_frame_exchange(
|
||||
const FelicaPoller* instance,
|
||||
const BitBuffer* tx_buffer,
|
||||
BitBuffer* rx_buffer,
|
||||
uint32_t fwt);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
+1
-1
Submodule lib/stm32wb_copro updated: d8a6f1feb0...64a060d91f
Reference in New Issue
Block a user