NFC: Support creating CC and NDEF files on DESFire

This commit is contained in:
Willy-JL
2025-03-20 07:21:36 +00:00
parent c4f220625c
commit f01972ea71
5 changed files with 205 additions and 25 deletions

View File

@@ -17,7 +17,12 @@ extern "C" {
#define MF_DESFIRE_CMD_GET_FILE_IDS (0x6F)
#define MF_DESFIRE_CMD_GET_FILE_SETTINGS (0xF5)
#define MF_DESFIRE_CMD_CREATE_APPLICATION (0xCA)
#define MF_DESFIRE_CMD_CREATE_APPLICATION (0xCA)
#define MF_DESFIRE_CMD_CREATE_STD_DATA_FILE (0xCD)
#define MF_DESFIRE_CMD_CREATE_BACKUP_DATA_FILE (0xCB)
#define MF_DESFIRE_CMD_CREATE_VALUE_FILE (0xCC)
#define MF_DESFIRE_CMD_CREATE_LINEAR_RECORD_FILE (0xC1)
#define MF_DESFIRE_CMD_CREATE_CYCLIC_RECORD_FILE (0xC0)
#define MF_DESFIRE_CMD_READ_DATA (0xBD)
#define MF_DESFIRE_CMD_GET_VALUE (0x6C)

View File

@@ -220,9 +220,9 @@ MfDesfireError mf_desfire_poller_read_file_settings_multi(
* @param[in, out] instance pointer to the instance to be used in the transaction.
* @param[in] id pointer to the application id for the new application.
* @param[in] key_settings pointer to the key settings for the new application.
* @param[in] iso_df_id optional identifier for the new application.
* @param[in] iso_df_name optional name for the new application.
* @param[in] iso_df_name_len length of the optional application name.
* @param[in] iso_df_id optional iso identifier for the new application.
* @param[in] iso_df_name optional iso name for the new application.
* @param[in] iso_df_name_len length of the optional iso application name.
* @return MfDesfireErrorNone on success, an error code on failure.
*/
MfDesfireError mf_desfire_poller_create_application(
@@ -233,6 +233,23 @@ MfDesfireError mf_desfire_poller_create_application(
const uint8_t* iso_df_name,
uint8_t iso_df_name_len);
/**
* @brief Create File on MfDesfire card.
*
* Must ONLY be used inside the callback function.
*
* @param[in, out] instance pointer to the instance to be used in the transaction.
* @param[in] id file id for the new file.
* @param[in] data pointer to the file settings for the new file.
* @param[in] iso_ef_id optional iso identifier for the new file.
* @return MfDesfireErrorNone on success, an error code on failure.
*/
MfDesfireError mf_desfire_poller_create_file(
MfDesfirePoller* instance,
MfDesfireFileId id,
const MfDesfireFileSettings* data,
uint16_t iso_ef_id);
/**
* @brief Read file data on MfDesfire card.
*

View File

@@ -405,6 +405,7 @@ MfDesfireError mf_desfire_poller_create_application(
const uint8_t* iso_df_name,
uint8_t iso_df_name_len) {
furi_check(instance);
furi_check(key_settings);
bit_buffer_reset(instance->input_buffer);
bit_buffer_append_byte(instance->input_buffer, MF_DESFIRE_CMD_CREATE_APPLICATION);
@@ -418,9 +419,9 @@ MfDesfireError mf_desfire_poller_create_application(
ks2 |= (1 << 5); // Mark file id present
bit_buffer_set_byte(instance->input_buffer, ks2_pos, ks2);
uint8_t iso_file_id_le[sizeof(iso_df_id)];
bit_lib_num_to_bytes_le(iso_df_id, sizeof(iso_file_id_le), iso_file_id_le);
bit_buffer_append_bytes(instance->input_buffer, iso_file_id_le, sizeof(iso_file_id_le));
uint8_t iso_df_id_le[sizeof(iso_df_id)];
bit_lib_num_to_bytes_le(iso_df_id, sizeof(iso_df_id_le), iso_df_id_le);
bit_buffer_append_bytes(instance->input_buffer, iso_df_id_le, sizeof(iso_df_id_le));
bit_buffer_append_bytes(instance->input_buffer, iso_df_name, iso_df_name_len);
}
@@ -431,6 +432,73 @@ MfDesfireError mf_desfire_poller_create_application(
return error;
}
MfDesfireError mf_desfire_poller_create_file(
MfDesfirePoller* instance,
MfDesfireFileId id,
const MfDesfireFileSettings* data,
uint16_t iso_ef_id) {
furi_check(instance);
furi_check(data);
bit_buffer_reset(instance->input_buffer);
bit_buffer_append_byte(
instance->input_buffer,
data->type == MfDesfireFileTypeStandard ? MF_DESFIRE_CMD_CREATE_STD_DATA_FILE :
data->type == MfDesfireFileTypeBackup ? MF_DESFIRE_CMD_CREATE_BACKUP_DATA_FILE :
data->type == MfDesfireFileTypeValue ? MF_DESFIRE_CMD_CREATE_VALUE_FILE :
data->type == MfDesfireFileTypeLinearRecord ? MF_DESFIRE_CMD_CREATE_LINEAR_RECORD_FILE :
data->type == MfDesfireFileTypeCyclicRecord ? MF_DESFIRE_CMD_CREATE_CYCLIC_RECORD_FILE :
0x00);
bit_buffer_append_byte(instance->input_buffer, id);
if(iso_ef_id) {
uint8_t iso_ef_id_le[sizeof(iso_ef_id)];
bit_lib_num_to_bytes_le(iso_ef_id, sizeof(iso_ef_id_le), iso_ef_id_le);
bit_buffer_append_bytes(instance->input_buffer, iso_ef_id_le, sizeof(iso_ef_id_le));
}
bit_buffer_append_byte(instance->input_buffer, data->comm);
bit_buffer_append_bytes(
instance->input_buffer,
(const uint8_t*)data->access_rights,
sizeof(MfDesfireFileAccessRights) * data->access_rights_len);
if(data->type == MfDesfireFileTypeStandard || data->type == MfDesfireFileTypeBackup) {
uint8_t data_size_le[3 * sizeof(uint8_t)];
bit_lib_num_to_bytes_le(data->data.size, sizeof(data_size_le), data_size_le);
bit_buffer_append_bytes(instance->input_buffer, data_size_le, sizeof(data_size_le));
} else if(data->type == MfDesfireFileTypeValue) {
uint8_t lo_limit_le[sizeof(data->value.lo_limit)];
bit_lib_num_to_bytes_le(data->value.lo_limit, sizeof(lo_limit_le), lo_limit_le);
bit_buffer_append_bytes(instance->input_buffer, lo_limit_le, sizeof(lo_limit_le));
uint8_t hi_limit_le[sizeof(data->value.hi_limit)];
bit_lib_num_to_bytes_le(data->value.hi_limit, sizeof(hi_limit_le), hi_limit_le);
bit_buffer_append_bytes(instance->input_buffer, hi_limit_le, sizeof(hi_limit_le));
uint8_t value_le[sizeof(data->value.limited_credit_value)];
bit_lib_num_to_bytes_le(data->value.limited_credit_value, sizeof(value_le), value_le);
bit_buffer_append_bytes(instance->input_buffer, value_le, sizeof(value_le));
bit_buffer_append_byte(instance->input_buffer, data->value.limited_credit_enabled);
} else if(
data->type == MfDesfireFileTypeLinearRecord ||
data->type == MfDesfireFileTypeCyclicRecord) {
uint8_t record_size_le[3 * sizeof(uint8_t)];
bit_lib_num_to_bytes_le(data->record.size, sizeof(record_size_le), record_size_le);
bit_buffer_append_bytes(instance->input_buffer, record_size_le, sizeof(record_size_le));
uint8_t record_max_le[3 * sizeof(uint8_t)];
bit_lib_num_to_bytes_le(data->record.max, sizeof(record_max_le), record_max_le);
bit_buffer_append_bytes(instance->input_buffer, record_max_le, sizeof(record_max_le));
}
MfDesfireError error =
mf_desfire_poller_send_chunks(instance, instance->input_buffer, instance->result_buffer);
return error;
}
static MfDesfireError mf_desfire_poller_read_file(
MfDesfirePoller* instance,
MfDesfireFileId id,

View File

@@ -10,7 +10,31 @@
#define TAG "Type4TagPoller"
static const MfDesfireApplicationId mf_des_picc_app_id = {.data = {0x00, 0x00, 0x00}};
static const MfDesfireApplicationId mf_des_ndef_app_id = {.data = {0x10, 0xEE, 0xEE}};
static const MfDesfireApplicationId mf_des_t4t_app_id = {.data = {0x10, 0xEE, 0xEE}};
static const MfDesfireKeySettings mf_des_t4t_app_key_settings = {
.is_master_key_changeable = true,
.is_free_directory_list = true,
.is_free_create_delete = true,
.is_config_changeable = true,
.change_key_id = 0,
.max_keys = 1,
.flags = 0,
};
#define MF_DES_T4T_CC_FILE_ID (0x01)
static const MfDesfireFileSettings mf_des_t4t_cc_file = {
.type = MfDesfireFileTypeStandard,
.comm = MfDesfireFileCommunicationSettingsPlaintext,
.access_rights[0] = 0xEEEE,
.access_rights_len = 1,
.data.size = TYPE_4_TAG_T4T_CC_MIN_SIZE,
};
#define MF_DES_T4T_NDEF_FILE_ID (0x02)
static const MfDesfireFileSettings mf_des_t4t_ndef_file_default = {
.type = MfDesfireFileTypeStandard,
.comm = MfDesfireFileCommunicationSettingsPlaintext,
.access_rights[0] = 0xEEE0,
.access_rights_len = 1,
};
Type4TagError type_4_tag_apdu_trx(Type4TagPoller* instance, BitBuffer* tx_buf, BitBuffer* rx_buf) {
furi_check(instance);
@@ -319,27 +343,18 @@ Type4TagError type_4_tag_poller_create_app(Type4TagPoller* instance) {
MfDesfireError mf_des_error;
do {
// Select PICC (Card) level
FURI_LOG_D(TAG, "Select DESFire PICC");
mf_des_error = mf_desfire_poller_select_application(mf_des, &mf_des_picc_app_id);
if(mf_des_error != MfDesfireErrorNone) {
error = Type4TagErrorProtocol;
break;
}
// Create NDEF application
MfDesfireKeySettings key_settings = {
.is_master_key_changeable = true,
.is_free_directory_list = true,
.is_free_create_delete = true,
.is_config_changeable = true,
.change_key_id = 0,
.max_keys = 1,
.flags = 0,
};
FURI_LOG_D(TAG, "Create DESFire T4T app");
mf_des_error = mf_desfire_poller_create_application(
mf_des,
&mf_des_ndef_app_id,
&key_settings,
&mf_des_t4t_app_id,
&mf_des_t4t_app_key_settings,
TYPE_4_TAG_ISO_DF_ID,
type_4_tag_iso_df_name,
sizeof(type_4_tag_iso_df_name));
@@ -363,13 +378,87 @@ Type4TagError type_4_tag_poller_create_app(Type4TagPoller* instance) {
}
Type4TagError type_4_tag_poller_create_cc(Type4TagPoller* instance) {
UNUSED(instance);
return Type4TagErrorNotSupported;
Type4TagError error = Type4TagErrorNotSupported;
if(instance->data->platform == Type4TagPlatformMfDesfire) {
MfDesfirePoller* mf_des = mf_desfire_poller.alloc(instance->iso14443_4a_poller);
mf_desfire_poller_set_command_mode(mf_des, MfDesfirePollerCommandModeIsoWrapped);
MfDesfireError mf_des_error;
do {
FURI_LOG_D(TAG, "Create DESFire CC");
mf_des_error = mf_desfire_poller_create_file(
mf_des, MF_DES_T4T_CC_FILE_ID, &mf_des_t4t_cc_file, TYPE_4_TAG_T4T_CC_EF_ID);
if(mf_des_error != MfDesfireErrorNone) {
if(mf_des_error != MfDesfireErrorNotPresent &&
mf_des_error != MfDesfireErrorTimeout) {
error = Type4TagErrorCardLocked;
} else {
error = Type4TagErrorProtocol;
}
break;
}
FURI_LOG_D(TAG, "Select CC");
error = type_5_tag_poller_iso_select_file(instance, TYPE_4_TAG_T4T_CC_EF_ID);
if(error != Type4TagErrorNone) break;
FURI_LOG_D(TAG, "Write DESFire CC");
instance->data->t4t_version.value = TYPE_4_TAG_T4T_CC_VNO;
instance->data->chunk_max_read = 0x3A;
instance->data->chunk_max_write = 0x34;
instance->data->ndef_file_id = TYPE_4_TAG_T4T_NDEF_EF_ID;
instance->data->ndef_max_len = TYPE_4_TAG_DEFAULT_NDEF_SIZE;
instance->data->ndef_read_lock = TYPE_4_TAG_T4T_CC_RW_LOCK_NONE;
instance->data->ndef_write_lock = TYPE_4_TAG_T4T_CC_RW_LOCK_NONE;
instance->data->is_tag_specific = true;
uint8_t cc_buf[TYPE_4_TAG_T4T_CC_MIN_SIZE];
type_4_tag_cc_dump(instance->data, cc_buf, sizeof(cc_buf));
error = type_5_tag_poller_iso_write(instance, 0, sizeof(cc_buf), cc_buf);
if(error != Type4TagErrorNone) break;
error = Type4TagErrorNone;
} while(false);
mf_desfire_poller.free(mf_des);
}
return error;
}
Type4TagError type_4_tag_poller_create_ndef(Type4TagPoller* instance) {
UNUSED(instance);
return Type4TagErrorNotSupported;
Type4TagError error = Type4TagErrorNotSupported;
if(instance->data->platform == Type4TagPlatformMfDesfire) {
MfDesfirePoller* mf_des = mf_desfire_poller.alloc(instance->iso14443_4a_poller);
mf_desfire_poller_set_command_mode(mf_des, MfDesfirePollerCommandModeIsoWrapped);
MfDesfireError mf_des_error;
do {
FURI_LOG_D(TAG, "Create DESFire NDEF");
MfDesfireFileSettings mf_des_t4t_ndef_file = mf_des_t4t_ndef_file_default;
mf_des_t4t_ndef_file.data.size = sizeof(uint16_t) + (instance->data->is_tag_specific ?
instance->data->ndef_max_len :
TYPE_4_TAG_DEFAULT_NDEF_SIZE);
mf_des_error = mf_desfire_poller_create_file(
mf_des, MF_DES_T4T_NDEF_FILE_ID, &mf_des_t4t_ndef_file, TYPE_4_TAG_T4T_NDEF_EF_ID);
if(mf_des_error != MfDesfireErrorNone) {
if(mf_des_error != MfDesfireErrorNotPresent &&
mf_des_error != MfDesfireErrorTimeout) {
error = Type4TagErrorCardLocked;
} else {
error = Type4TagErrorProtocol;
}
break;
}
error = Type4TagErrorNone;
} while(false);
mf_desfire_poller.free(mf_des);
}
return error;
}
Type4TagError type_4_tag_poller_write_ndef(Type4TagPoller* instance) {

View File

@@ -2637,6 +2637,7 @@ Function,+,mf_desfire_get_uid,const uint8_t*,"const MfDesfireData*, size_t*"
Function,+,mf_desfire_is_equal,_Bool,"const MfDesfireData*, const MfDesfireData*"
Function,+,mf_desfire_load,_Bool,"MfDesfireData*, FlipperFormat*, uint32_t"
Function,+,mf_desfire_poller_create_application,MfDesfireError,"MfDesfirePoller*, const MfDesfireApplicationId*, const MfDesfireKeySettings*, uint16_t, const uint8_t*, uint8_t"
Function,+,mf_desfire_poller_create_file,MfDesfireError,"MfDesfirePoller*, MfDesfireFileId, const MfDesfireFileSettings*, uint16_t"
Function,+,mf_desfire_poller_read_application,MfDesfireError,"MfDesfirePoller*, MfDesfireApplication*"
Function,+,mf_desfire_poller_read_application_ids,MfDesfireError,"MfDesfirePoller*, SimpleArray*"
Function,+,mf_desfire_poller_read_applications,MfDesfireError,"MfDesfirePoller*, const SimpleArray*, SimpleArray*"
1 entry status name type params
2637 Function + mf_desfire_is_equal _Bool const MfDesfireData*, const MfDesfireData*
2638 Function + mf_desfire_load _Bool MfDesfireData*, FlipperFormat*, uint32_t
2639 Function + mf_desfire_poller_create_application MfDesfireError MfDesfirePoller*, const MfDesfireApplicationId*, const MfDesfireKeySettings*, uint16_t, const uint8_t*, uint8_t
2640 Function + mf_desfire_poller_create_file MfDesfireError MfDesfirePoller*, MfDesfireFileId, const MfDesfireFileSettings*, uint16_t
2641 Function + mf_desfire_poller_read_application MfDesfireError MfDesfirePoller*, MfDesfireApplication*
2642 Function + mf_desfire_poller_read_application_ids MfDesfireError MfDesfirePoller*, SimpleArray*
2643 Function + mf_desfire_poller_read_applications MfDesfireError MfDesfirePoller*, const SimpleArray*, SimpleArray*