/* * * neard - Near Field Communication manager * * Copyright (C) 2011 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0])) #define NFC_STATUS 0 #define NFC_STATUS_BYTE_LEN 1 #define STATUS_WORD_1 1 #define STATUS_WORD_2 2 #define APDU_HEADER_LEN 5 #define APDU_OK 0x9000 #define APDU_NOT_FOUND 0x6A82 /* PICC Level Commands */ #define PICC_CLASS 0x90 #define GET_VERSION 0x60 #define CREATE_APPLICATION 0xCA #define SELECT_APPLICATION 0x5A #define CREATE_STD_DATA_FILE 0xCD #define WRITE_DATA_TO_FILE 0x3D #define DESFire_EV1_MAJOR_VERSION 0x01 #define PICC_LEVEL_APDU_OK 0x9100 #define GET_VERSION_FRAME_RESPONSE_BYTE 0xAF #define DESFIRE_KEY_SETTINGS 0x0F #define DESFIRE_NUM_OF_KEYS 0x21 #define DESFIRE_CC_FILE_NUM 0x01 #define DESFIRE_NDEF_FILE_NUM 0x02 #define DESFIRE_COMMSET 0x00 #define MAPPING_VERSION 0x20 #define FREE_READ_ACCESS 0x00 #define FREE_WRITE_ACCESS 0x00 #define T4_ALL_ACCESS 0x00 #define T4_READ_ONLY 0xFF #define APDU_STATUS(a) near_get_be16(a) /* Tag Type 4 version ID */ static uint8_t iso_appname_v1[] = { 0xd2, 0x76, 0x0, 0x0, 0x85, 0x01, 0x0 }; static uint8_t iso_appname_v2[] = { 0xd2, 0x76, 0x0, 0x0, 0x85, 0x01, 0x1 }; /* Tag T4 File ID */ static uint8_t iso_cc_fileid[] = { 0xe1, 0x03 }; #define LEN_ISO_CC_FILEID 2 #define LEN_ISO_CC_READ_SIZE 0x0F #define CMD_HEADER_SIZE 5 struct type4_cmd { /* iso 7816 */ uint8_t class; uint8_t instruction; uint8_t param1; uint8_t param2; uint8_t data_length; uint8_t data[]; } __attribute__((packed)); struct type4_NDEF_file_control_tlv { uint8_t tag ; /* should be 4 */ uint8_t len ; /* should be 6 */ uint16_t file_id ; uint16_t max_ndef_size ; uint8_t read_access ; uint8_t write_access ; } __attribute__((packed)); struct type4_cc { /* Capability Container */ uint16_t CCLEN; uint8_t mapping_version; uint16_t max_R_apdu_data_size; uint16_t max_C_apdu_data_size; struct type4_NDEF_file_control_tlv tlv_fc ; uint8_t tlv_blocks[]; } __attribute__((packed)); struct desfire_app { uint8_t aid[3]; uint8_t key_settings; uint8_t number_of_keys; uint8_t file_id[2]; uint8_t iso_appname[7]; } __attribute__((packed)); struct desfire_std_file { uint8_t file_num; uint8_t file_id[2]; uint8_t comm_set; uint8_t access_rights[2]; uint8_t size[3]; } __attribute__((packed)); struct desfire_cc_file { uint8_t file_num; uint8_t offset[3]; uint8_t max_len[3]; uint8_t cc_len[2]; uint8_t version; uint8_t mle[2]; uint8_t mlc[2]; uint8_t ndef_tlv[4]; uint8_t ndef_size[2]; uint8_t read_access; uint8_t write_access; } __attribute__((packed)); struct t4_cookie { uint32_t adapter_idx; uint32_t target_idx; near_tag_io_cb cb; struct near_tag *tag; uint16_t read_data; uint16_t r_apdu_max_size; uint16_t c_apdu_max_size; uint16_t max_ndef_size; uint8_t write_access; struct near_ndef_message *ndef; uint16_t memory_size; }; static int t4_cookie_release(int err, void *data) { struct t4_cookie *cookie = data; DBG("%p", cookie); if (!cookie) return err; if (err < 0 && cookie->cb) cookie->cb(cookie->adapter_idx, cookie->target_idx, err); if (cookie->ndef) g_free(cookie->ndef->data); g_free(cookie->ndef); g_free(cookie); return err; } /* ISO functions: This code prepares APDU */ static int ISO_send_cmd(uint8_t class, uint8_t instruction, uint8_t param1, uint8_t param2, uint8_t *cmd_data, uint8_t cmd_data_length, bool le, near_recv cb, void *in_data) { struct type4_cmd *cmd; struct t4_cookie *in_rcv = in_data; uint8_t total_cmd_length; int err; DBG("CLA-%02x INS-%02x P1-%02x P2-%02x", class, instruction, param1, param2); if (!le) { if (cmd_data) total_cmd_length = APDU_HEADER_LEN + cmd_data_length; else total_cmd_length = APDU_HEADER_LEN; } else { /* Extra byte for Le */ total_cmd_length = APDU_HEADER_LEN + cmd_data_length + 1; } cmd = g_try_malloc0(total_cmd_length); if (!cmd) { DBG("Mem alloc failed"); err = -ENOMEM; goto out_err; } cmd->class = class; cmd->instruction = instruction ; cmd->param1 = param1 ; cmd->param2 = param2 ; cmd->data_length = cmd_data_length; if (cmd_data) { memcpy(cmd->data, cmd_data, cmd_data_length); /* The Le byte set to 0x00 defined that any length * of PICC response is allowed */ if (le) cmd->data[cmd_data_length] = 0; } return near_adapter_send(in_rcv->adapter_idx, (uint8_t *) cmd, total_cmd_length, cb, in_rcv, t4_cookie_release); out_err: /* On exit, clean memory */ g_free(cmd); return err; } /* ISO 7816 command: Select applications or files * p1=0 select by "file id" * P1=4 select by "DF name" * */ static int ISO_Select(uint8_t *filename, uint8_t fnamelen, uint8_t P1, near_recv cb, void *cookie) { DBG(""); return ISO_send_cmd( 0x00, /* CLA */ 0xA4, /* INS: Select file */ P1, /* P1: select by name */ 0x00, /* P2: First or only occurrence */ filename, /* cmd_data */ fnamelen, /* uint8_t cmd_data_length*/ false, cb, cookie); } /* ISO 7816 command: Read binary data from files */ static int ISO_ReadBinary(uint16_t offset, uint8_t readsize, near_recv cb, void *cookie) { DBG(""); return ISO_send_cmd( 0x00, /* CLA */ 0xB0, /* INS: Select file */ (uint8_t) ((offset & 0xFF00) >> 8), (uint8_t) (offset & 0xFF), 0, /* no data send */ readsize, /* bytes to read */ false, cb, cookie); } /* ISO 7816 command: Update data */ static int ISO_Update(uint16_t offset, uint8_t nlen, uint8_t *data, near_recv cb, void *cookie) { DBG(""); return ISO_send_cmd( 0x00, /* CLA */ 0xD6, /* INS: Select file */ (uint8_t) ((offset & 0xFF00) >> 8), (uint8_t) (offset & 0xFF), data, /* length of NDEF data */ nlen, /* NLEN + NDEF data */ false, cb, cookie); } static int data_read_cb(uint8_t *resp, int length, void *data) { struct t4_cookie *cookie = data ; uint8_t *nfc_data; size_t data_length, length_read, current_length; uint16_t remain_bytes; DBG("%d", length); if (length < 0) return t4_cookie_release(length, cookie); if (APDU_STATUS(resp + length - 2) != APDU_OK) { DBG("Fail read_cb SW:x%04x", APDU_STATUS(resp + length - 2)); return t4_cookie_release(-EIO, cookie); } nfc_data = near_tag_get_data(cookie->tag, &data_length); /* Remove SW1 / SW2 and NFC header */ length_read = length - NFC_HEADER_SIZE - 2 ; length = length_read; current_length = cookie->read_data; if (current_length + (length_read) > data_length) length_read = data_length - current_length; memcpy(nfc_data + current_length, resp + NFC_HEADER_SIZE, length_read); if (current_length + length_read == data_length) { GList *records; DBG("Done reading"); records = near_ndef_parse_msg(nfc_data, data_length, NULL); near_tag_add_records(cookie->tag, records, cookie->cb, 0); return t4_cookie_release(0, cookie); } cookie->read_data += length ; remain_bytes = (data_length - cookie->read_data); if (remain_bytes >= cookie->r_apdu_max_size) return ISO_ReadBinary(cookie->read_data + 2, cookie->r_apdu_max_size, data_read_cb, cookie); else return ISO_ReadBinary(cookie->read_data + 2, (uint8_t) remain_bytes, data_read_cb, cookie); } static int t4_readbin_NDEF_ID(uint8_t *resp, int length, void *data) { struct t4_cookie *cookie = data; struct near_tag *tag; int err; DBG("%d", length); if (length < 0) return t4_cookie_release(length, cookie); if (APDU_STATUS(resp + length - 2) != APDU_OK) { DBG("Fail SW:x%04x", APDU_STATUS(resp + length - 2)); return t4_cookie_release(-EIO, cookie); } /* Add data to the tag */ err = near_tag_add_data(cookie->adapter_idx, cookie->target_idx, NULL, near_get_be16(resp + NFC_STATUS_BYTE_LEN)); if (err < 0) return t4_cookie_release(err, cookie); tag = near_tag_get_tag(cookie->adapter_idx, cookie->target_idx); if (!tag) return t4_cookie_release(-ENOMEM, cookie); near_tag_set_max_ndef_size(tag, cookie->max_ndef_size); near_tag_set_c_apdu_max_size(tag, cookie->c_apdu_max_size); near_tag_set_blank(tag, FALSE); /* save the tag */ cookie->tag = tag; /* Set write conditions */ if (cookie->write_access == T4_READ_ONLY) near_tag_set_ro(tag, TRUE); else near_tag_set_ro(tag, FALSE); /* * TODO: see how we can get the UID value: * near_tag_set_uid(tag, resp + NFC_HEADER_SIZE, 8); */ /* Read 1st block */ return ISO_ReadBinary(2, cookie->r_apdu_max_size - 2, data_read_cb, cookie); } static int t4_select_NDEF_ID(uint8_t *resp, int length, void *data) { struct t4_cookie *cookie = data; DBG("%d", length); if (length < 0) return t4_cookie_release(length, cookie); /* Check for APDU error */ if (APDU_STATUS(resp + STATUS_WORD_1) != APDU_OK) { DBG("Fail SW:x%04x", APDU_STATUS(resp + STATUS_WORD_1)); return t4_cookie_release(-EIO, cookie); } /* Read 0x0f bytes, to grab the NDEF msg length */ return ISO_ReadBinary(0, LEN_ISO_CC_READ_SIZE, t4_readbin_NDEF_ID, cookie); } static int t4_readbin_cc(uint8_t *resp, int length, void *data) { struct t4_cookie *cookie = data; struct type4_cc *read_cc = (struct type4_cc *)&resp[1]; DBG("%d", length); if (length < 0) return t4_cookie_release(length, cookie); /* Check APDU error ( the two last bytes of the resp) */ if (APDU_STATUS(resp + length - 2) != APDU_OK) { DBG("Fail SW:x%04x", APDU_STATUS(resp + length - 2)); return t4_cookie_release(-EIO, cookie); } cookie->r_apdu_max_size = g_ntohs(read_cc->max_R_apdu_data_size) - APDU_HEADER_LEN; cookie->c_apdu_max_size = g_ntohs(read_cc->max_C_apdu_data_size); cookie->max_ndef_size = g_ntohs(read_cc->tlv_fc.max_ndef_size); /* TODO 5.1.1: TLV blocks can be zero, one or more... */ /* TODO 5.1.2: Must ignore proprietary blocks (x05)... */ if (read_cc->tlv_fc.tag != 0x4) { DBG("NDEF File Control tag not found"); return t4_cookie_release(-EINVAL, cookie); } /* save rw conditions */ cookie->write_access = read_cc->tlv_fc.write_access; return ISO_Select((uint8_t *) &read_cc->tlv_fc.file_id, LEN_ISO_CC_FILEID, 0, t4_select_NDEF_ID, cookie); } static int t4_select_cc(uint8_t *resp, int length, void *data) { struct t4_cookie *cookie = data; int err; DBG("%d", length); if (length < 0) return t4_cookie_release(length, cookie); /* Check for APDU error */ if (APDU_STATUS(resp + STATUS_WORD_1) != APDU_OK) { DBG(" Found empty tag"); /* Add data to the tag */ err = near_tag_add_data(cookie->adapter_idx, cookie->target_idx, NULL, 1 /* dummy length */); if (err < 0) return t4_cookie_release(err, cookie); cookie->tag = near_tag_get_tag(cookie->adapter_idx, cookie->target_idx); if (!cookie->tag) return t4_cookie_release(-ENOMEM, cookie); near_tag_set_blank(cookie->tag, TRUE); if (cookie->cb) cookie->cb(cookie->adapter_idx, cookie->target_idx, 0); return t4_cookie_release(0, cookie); } return ISO_ReadBinary(0, LEN_ISO_CC_READ_SIZE, t4_readbin_cc, cookie); } static int t4_select_file_by_name_v1(uint8_t *resp, int length, void *data) { struct t4_cookie *cookie = data; DBG("%d", length); if (length < 0) return t4_cookie_release(length, cookie); /* Check for APDU error */ if (APDU_STATUS(resp + STATUS_WORD_1) != APDU_OK) { DBG("V1 Fail SW:x%04x", APDU_STATUS(resp + STATUS_WORD_1)); return t4_cookie_release(-EIO, cookie); } if (resp[NFC_STATUS] != 0) return t4_cookie_release(-EIO, cookie); /* Jump to select phase */ return ISO_Select(iso_cc_fileid, LEN_ISO_CC_FILEID, 0, t4_select_cc, cookie); } static int t4_select_file_by_name_v2(uint8_t *resp, int length, void *data) { struct t4_cookie *cookie = data; DBG("%d", length); if (length < 0) return t4_cookie_release(length, cookie); /* Check for APDU error - Not found */ if (APDU_STATUS(resp + STATUS_WORD_1) == APDU_NOT_FOUND) { DBG("Fallback to V1"); return ISO_Select(iso_appname_v1, ARRAY_SIZE(iso_appname_v1), 0x4, t4_select_file_by_name_v1, cookie); } if (APDU_STATUS(resp + STATUS_WORD_1) != APDU_OK) { DBG("V2 Fail SW:x%04x", APDU_STATUS(resp + STATUS_WORD_1)); return t4_cookie_release(-EIO, cookie); } if (resp[NFC_STATUS] != 0) return t4_cookie_release(-EIO, cookie); /* Jump to select phase */ return ISO_Select(iso_cc_fileid, LEN_ISO_CC_FILEID, 0, t4_select_cc, cookie); } static int nfctype4_read(uint32_t adapter_idx, uint32_t target_idx, near_tag_io_cb cb) { struct t4_cookie *cookie; DBG(""); cookie = g_try_malloc0(sizeof(struct t4_cookie)); if (!cookie) return -ENOMEM; cookie->adapter_idx = adapter_idx; cookie->target_idx = target_idx; cookie->cb = cb; cookie->tag = NULL; cookie->read_data = 0; /* Check for V2 type 4 tag */ return ISO_Select(iso_appname_v2, ARRAY_SIZE(iso_appname_v2), 0x4, t4_select_file_by_name_v2, cookie); } static int data_write_cb(uint8_t *resp, int length, void *data) { struct t4_cookie *cookie = data; int err = 0; DBG("%d", length); if (length < 0) return t4_cookie_release(length, cookie); if (APDU_STATUS(resp + length - 2) != APDU_OK) { near_error("write failed SWx%04x", APDU_STATUS(resp + length - 2)); return t4_cookie_release(-EIO, cookie); } if (cookie->ndef->offset >= cookie->ndef->length) { DBG("Done writing"); if (cookie->cb) cookie->cb(cookie->adapter_idx, cookie->target_idx, 0); return t4_cookie_release(0, cookie); } if ((cookie->ndef->length - cookie->ndef->offset) > cookie->c_apdu_max_size) { err = ISO_Update(cookie->ndef->offset, cookie->c_apdu_max_size, cookie->ndef->data + cookie->ndef->offset, data_write_cb, cookie); cookie->ndef->offset += cookie->c_apdu_max_size; } else { err = ISO_Update(cookie->ndef->offset, cookie->ndef->length - cookie->ndef->offset, cookie->ndef->data + cookie->ndef->offset, data_write_cb, cookie); cookie->ndef->offset = cookie->ndef->length; } if (err < 0) return t4_cookie_release(err, cookie); return err; } static int data_write(uint32_t adapter_idx, uint32_t target_idx, struct near_ndef_message *ndef, struct near_tag *tag, near_tag_io_cb cb) { struct t4_cookie *cookie; int err; cookie = g_try_malloc0(sizeof(struct t4_cookie)); if (!cookie) { err = -ENOMEM; if (cb) cb(adapter_idx, target_idx, err); return err; } cookie->adapter_idx = adapter_idx; cookie->target_idx = target_idx; cookie->cb = cb; cookie->tag = NULL; cookie->read_data = 0; cookie->max_ndef_size = near_tag_get_max_ndef_size(tag); cookie->c_apdu_max_size = near_tag_get_c_apdu_max_size(tag); cookie->ndef = ndef; if (cookie->max_ndef_size < cookie->ndef->length) { near_error("not enough space on tag to write data"); return t4_cookie_release(-ENOMEM, cookie); } if ((cookie->ndef->length - cookie->ndef->offset) > cookie->c_apdu_max_size) { err = ISO_Update(cookie->ndef->offset, cookie->c_apdu_max_size, cookie->ndef->data, data_write_cb, cookie); cookie->ndef->offset += cookie->c_apdu_max_size; } else { err = ISO_Update(cookie->ndef->offset, cookie->ndef->length, cookie->ndef->data, data_write_cb, cookie); cookie->ndef->offset = cookie->ndef->length; } if (err < 0) goto out_err; return 0; out_err: return t4_cookie_release(err, cookie); } static int nfctype4_write(uint32_t adapter_idx, uint32_t target_idx, struct near_ndef_message *ndef, near_tag_io_cb cb) { struct near_tag *tag; int err; DBG(""); if (!ndef || !cb) { err = -EINVAL; goto out_err; } tag = near_tag_get_tag(adapter_idx, target_idx); if (!tag) { err = -EINVAL; goto out_err; } err = data_write(adapter_idx, target_idx, ndef, tag, cb); out_err: if (cb) cb(adapter_idx, target_idx, err); return err; } static int check_presence(uint8_t *resp, int length, void *data) { struct t4_cookie *cookie = data; int err = 0; DBG("%d", length); if (length < 0) err = -EIO; if (cookie->cb) cookie->cb(cookie->adapter_idx, cookie->target_idx, err); return t4_cookie_release(err, cookie); } static int nfctype4_check_presence(uint32_t adapter_idx, uint32_t target_idx, near_tag_io_cb cb) { struct t4_cookie *cookie; DBG(""); cookie = g_try_malloc0(sizeof(struct t4_cookie)); if (!cookie) return -ENOMEM; cookie->adapter_idx = adapter_idx; cookie->target_idx = target_idx; cookie->cb = cb; cookie->tag = NULL; cookie->read_data = 0; /* Check for V2 type 4 tag */ return ISO_Select(iso_appname_v2, ARRAY_SIZE(iso_appname_v2), 0x4, check_presence, cookie); } static int select_ndef_file(uint8_t *resp, int length, void *data) { struct t4_cookie *cookie = data; DBG("%d", length); if (length < 0) return t4_cookie_release(length, cookie); if (APDU_STATUS(resp + STATUS_WORD_1) != APDU_OK) { near_error("select ndef file resp failed %02X", resp[length - 1]); return t4_cookie_release(-EIO, cookie); } DBG("ndef file selected"); if (cookie->cb) cookie->cb(cookie->adapter_idx, cookie->target_idx, 0); return t4_cookie_release(0, cookie); } static int read_cc_file(uint8_t *resp, int length, void *data) { struct t4_cookie *cookie = data; struct near_tag *tag; struct type4_cc *read_cc = NULL; int err = 0; DBG("%d", length); if (length < 0) { err = length; goto out_err; } /* Check APDU error ( the two last bytes of the resp) */ if (APDU_STATUS(resp + length - 2) != APDU_OK) { near_error("read cc failed SWx%04x", APDU_STATUS(resp + length - 2)); err = -EIO; goto out_err; } /* -2 for status word and -1 is for NFC first byte... */ read_cc = g_try_malloc0(length - 2 - NFC_STATUS_BYTE_LEN); if (!read_cc) { err = -ENOMEM; goto out_err; } memcpy(read_cc, &resp[1], length - 2 - NFC_STATUS_BYTE_LEN) ; cookie->c_apdu_max_size = g_ntohs(read_cc->max_C_apdu_data_size); cookie->max_ndef_size = g_ntohs(read_cc->tlv_fc.max_ndef_size); tag = near_tag_get_tag(cookie->adapter_idx, cookie->target_idx); if (!tag) { err = -EINVAL; goto out_err; } near_tag_set_max_ndef_size(tag, cookie->memory_size); near_tag_set_c_apdu_max_size(tag, cookie->c_apdu_max_size); if (read_cc->tlv_fc.tag != 0x4) { near_error("NDEF File not found") ; err = -EINVAL ; goto out_err; } err = ISO_Select((uint8_t *)&read_cc->tlv_fc.file_id, LEN_ISO_CC_FILEID, 0, select_ndef_file, cookie); if (err < 0) { near_error("select ndef file req failed %d", err); goto out_err; } g_free(read_cc); return 0; out_err: g_free(read_cc); return t4_cookie_release(err, cookie); } static int select_cc_file(uint8_t *resp, int length, void *data) { int err = 0; struct t4_cookie *cookie = data; if (length < 0) { near_error("CC file select resp failed %d", length); return t4_cookie_release(length, cookie); } if (APDU_STATUS(resp + STATUS_WORD_1) != APDU_OK) { near_error("CC file select response %02X", resp[length - 1]); return t4_cookie_release(-EIO, cookie); } err = ISO_ReadBinary(0, LEN_ISO_CC_READ_SIZE, read_cc_file, cookie); if (err < 0) { near_error("read cc file req failed %d", err); goto out_err; } return 0; out_err: return t4_cookie_release(err, cookie); } static int select_iso_appname_v2(uint8_t *resp, int length, void *data) { int err = 0; struct t4_cookie *cookie = data; if (length < 0) { near_error("iso app select resp failed %d", length); return t4_cookie_release(length, cookie); } if (resp[NFC_STATUS] != 0x00) { near_error("iso app select response %02X", resp[length - 1]); return t4_cookie_release(-EIO, cookie); } err = ISO_Select(iso_cc_fileid, LEN_ISO_CC_FILEID, 0, select_cc_file, cookie); if (err < 0) { near_error("select cc req failed %d", err); goto out_err; } return 0; out_err: return t4_cookie_release(err, cookie); } static int format_resp(uint8_t *resp, int length, void *data) { int err = 0; struct t4_cookie *cookie = data; struct near_tag *tag; DBG(""); if (length < 0) { near_error("write data to ndef file resp failed %d", length); return t4_cookie_release(length, cookie); } if (APDU_STATUS(resp + 1) != PICC_LEVEL_APDU_OK) { near_error("wrtie data to ndef file response %02X", resp[length - 1]); return t4_cookie_release(-EIO, cookie); } tag = near_tag_get_tag(cookie->adapter_idx, cookie->target_idx); if (!tag) return t4_cookie_release(-EINVAL, cookie); DBG("Formatting is done"); near_tag_set_blank(tag, FALSE); /* * 1) Till now all commands which are used for formatting are * at mifare desfire level. Now select iso appname_v2, * cc file and ndef file with ISO 7816-4 commands. * 2) Selecting ndef file means making sure that read write * operations will perform on NDEF file. */ err = ISO_Select(iso_appname_v2, ARRAY_SIZE(iso_appname_v2), 0x4, select_iso_appname_v2, cookie); if (err < 0) { near_error("iso_select appnamev2 req failed %d", err); goto out_err; } return err; out_err: return t4_cookie_release(err, cookie); } static int write_data_to_ndef_file(uint8_t *resp, int length, void *data) { int err = 0; struct t4_cookie *cookie = data; uint8_t *cmd_data = NULL; uint8_t cmd_data_length; uint8_t ndef_file_offset[] = {0x00, 0x00, 0x00}; uint8_t empty_ndef_file_len[] = {0x02, 0x00, 0x00}; /* 000002h */ uint8_t ndef_nlen[] = {0x00, 0x00}; /* 0000h */ DBG(""); if (length < 0) { near_error("create ndef file resp failed %d", length); err = length; goto out_err; } if (APDU_STATUS(resp + 1) != PICC_LEVEL_APDU_OK) { near_error("create ndef file response %02X", resp[length - 1]); err = -EIO; goto out_err; } /* Step8 : Write data to NDEF file ( no NDEF message) */ cmd_data_length = 1 /* File num */ + ARRAY_SIZE(ndef_file_offset) + ARRAY_SIZE(empty_ndef_file_len) + ARRAY_SIZE(ndef_nlen); cmd_data = g_try_malloc0(cmd_data_length); if (!cmd_data) { err = -ENOMEM; goto out_err; } cmd_data[0] = DESFIRE_NDEF_FILE_NUM; memcpy(cmd_data + 1, ndef_file_offset, ARRAY_SIZE(ndef_file_offset)); memcpy(cmd_data + 4, empty_ndef_file_len, ARRAY_SIZE(empty_ndef_file_len)); memcpy(cmd_data + 7, ndef_nlen, ARRAY_SIZE(ndef_nlen)); err = ISO_send_cmd(PICC_CLASS, WRITE_DATA_TO_FILE, 0x00, 0x00, cmd_data, cmd_data_length, true, format_resp, cookie); if (err < 0) { near_error("wrtie data to ndef file req failed %d", err); goto out_err; } g_free(cmd_data); return 0; out_err: g_free(cmd_data); return t4_cookie_release(err, cookie); } static int create_ndef_file(uint8_t *resp, int length, void *data) { int err = 0; struct t4_cookie *cookie = data; struct desfire_std_file *ndef = NULL; uint8_t iso_ndef_file_id[] = {0x04, 0xE1}; /* E104h */ uint8_t ndef_file_access_rights[] = {0xE0, 0xEE}; /* EEE0h */ DBG(""); if (length < 0) { near_error("write data to cc file resp failed %d", length); err = length; goto out_err; } if (APDU_STATUS(resp + 1) != PICC_LEVEL_APDU_OK) { near_error("write data to cc file response %02X", resp[length - 1]); err = -EIO; goto out_err; } ndef = g_try_malloc0(sizeof(struct desfire_std_file)); if (!ndef) { err = -ENOMEM; goto out_err; } ndef->file_num = DESFIRE_NDEF_FILE_NUM; memcpy(ndef->file_id, iso_ndef_file_id, ARRAY_SIZE(iso_ndef_file_id)); ndef->comm_set = DESFIRE_COMMSET; memcpy(ndef->access_rights, ndef_file_access_rights, ARRAY_SIZE(ndef_file_access_rights)); ndef->size[0] = 0; ndef->size[1] = (uint8_t) (cookie->memory_size >> 8); ndef->size[2] = (uint8_t) cookie->memory_size; err = ISO_send_cmd(PICC_CLASS, CREATE_STD_DATA_FILE, 0x00, 0x00, (uint8_t *)ndef, sizeof(struct desfire_std_file), true, write_data_to_ndef_file, cookie); if (err < 0) { near_error("create ndef file req failed %d", err); goto out_err; } g_free(ndef); return 0; out_err: g_free(ndef); return t4_cookie_release(err, cookie); } static int write_data_to_cc_file(uint8_t *resp, int length, void *data) { int err = 0; struct t4_cookie *cookie = data; struct desfire_cc_file *cc = NULL; uint8_t cc_file_offset[] = {0x00, 0x00, 0x00}; uint8_t cc_file_max_len[] = {0x0F, 0x00, 0x00}; /* 00000Fh */ uint8_t cc_len[] = {0x00, 0x0F}; /* 000Fh*/ uint8_t mle_r_apdu[] = {0x00, 0x3B}; /* 003Bh */ uint8_t mlc_c_apdu[] = {0x00, 0x34}; /* 0034h */ /* T: 04, L: 06: V: E104h (NDEF ISO FID = E104h)*/ uint8_t ndef_tlv[] = {0x04, 0x06, 0xE1, 0x04}; DBG(""); if (length < 0) { near_error("create cc file resp failed %d", length); err = length; goto out_err; } if (APDU_STATUS(resp + 1) != PICC_LEVEL_APDU_OK) { near_error("create cc file response %02X", resp[length - 1]); err = -EIO; goto out_err; } cc = g_try_malloc0(sizeof(struct desfire_cc_file)); if (!cc) { err = -ENOMEM; goto out_err; } cc->file_num = DESFIRE_CC_FILE_NUM; memcpy(cc->offset, cc_file_offset, ARRAY_SIZE(cc_file_offset)); memcpy(cc->max_len, cc_file_max_len, ARRAY_SIZE(cc_file_max_len)); memcpy(cc->cc_len, cc_len, ARRAY_SIZE(cc_len)); cc->version = MAPPING_VERSION; memcpy(cc->mle, mle_r_apdu, ARRAY_SIZE(mle_r_apdu)); memcpy(cc->mlc, mlc_c_apdu, ARRAY_SIZE(mlc_c_apdu)); memcpy(cc->ndef_tlv, ndef_tlv, ARRAY_SIZE(ndef_tlv)); cc->ndef_size[0] = (uint8_t) (cookie->memory_size >> 8); cc->ndef_size[1] = (uint8_t) cookie->memory_size; cc->read_access = FREE_READ_ACCESS; cc->write_access = FREE_WRITE_ACCESS; err = ISO_send_cmd(PICC_CLASS, WRITE_DATA_TO_FILE, 0x00, 0x00, (uint8_t *)cc, sizeof(struct desfire_cc_file), true, create_ndef_file, cookie); if (err < 0) { near_error("write data to cc file req failed %d", err); goto out_err; } g_free(cc); return 0; out_err: g_free(cc); return t4_cookie_release(err, cookie); } static int create_cc_file(uint8_t *resp, int length, void *data) { int err = 0; struct t4_cookie *cookie = data; struct desfire_std_file *cc = NULL; uint8_t iso_cc_file_id[] = {0x03, 0xe1}; /* E103h */ uint8_t cc_file_access_rights[] = {0xE0, 0xEE}; /* EEE0h */ uint8_t cc_file_max_len[] = {0x0F, 0x00, 0x00}; /* 00000Fh */ DBG(""); if (length < 0) { near_error("select application1 resp failed %d", length); err = length; goto out_err; } if (APDU_STATUS(resp + 1) != PICC_LEVEL_APDU_OK) { near_error("select application1 response %02X", resp[length - 1]); err = -EIO; goto out_err; } cc = g_try_malloc0(sizeof(struct desfire_std_file)); if (!cc) { err = -ENOMEM; goto out_err; } cc->file_num = DESFIRE_CC_FILE_NUM; memcpy(cc->file_id, iso_cc_file_id, ARRAY_SIZE(iso_cc_file_id)); cc->comm_set = DESFIRE_COMMSET; memcpy(cc->access_rights, cc_file_access_rights, ARRAY_SIZE(cc_file_access_rights)); memcpy(cc->size, cc_file_max_len, ARRAY_SIZE(cc_file_max_len)); err = ISO_send_cmd(PICC_CLASS, CREATE_STD_DATA_FILE, 0x00, 0x00, (uint8_t *)cc, sizeof(struct desfire_std_file), true, write_data_to_cc_file, cookie); if (err < 0) { near_error("create cc file req failed %d", err); goto out_err; } g_free(cc); return 0; out_err: g_free(cc); return t4_cookie_release(err, cookie); } static int select_application_1(uint8_t *resp, int length, void *data) { int err = 0; struct t4_cookie *cookie = data; uint8_t *cmd_data = NULL; uint8_t cmd_data_length; uint8_t desfire_aid_1[] = {0x01, 0x00, 0x00}; /* 000001h */ DBG(""); if (length < 0) { near_error("create application resp failed %d", length); err = length; goto out_err; } if (APDU_STATUS(resp + 1) != PICC_LEVEL_APDU_OK) { near_error("create application response %02X", resp[length - 1]); err = -EIO; goto out_err; } /* Step4 : Select application (which is created just now) */ cmd_data_length = ARRAY_SIZE(desfire_aid_1); cmd_data = g_try_malloc0(cmd_data_length); if (!cmd_data) { err = -ENOMEM; goto out_err; } memcpy(cmd_data, desfire_aid_1, cmd_data_length); err = ISO_send_cmd(PICC_CLASS, SELECT_APPLICATION, 0x00, 0x00, cmd_data, cmd_data_length, true, create_cc_file, cookie); if (err < 0) { near_error("select application1 req failed %d", err); goto out_err; } g_free(cmd_data); return 0; out_err: g_free(cmd_data); return t4_cookie_release(err, cookie); } static int create_application(uint8_t *resp, int length, void *data) { int err = 0; struct t4_cookie *cookie = data; uint8_t desfire_aid_1[] = {0x01, 0x00, 0x00}; /* 000001h */ uint8_t desfire_file_id[] = {0x10, 0xE1}; /* E110h */ struct desfire_app *app = NULL; DBG(""); if (length < 0) { near_error("select application resp failed %d", length); err = length; goto out_err; } if (APDU_STATUS(resp + 1) != PICC_LEVEL_APDU_OK) { near_error("select application response %02X", resp[length - 1]); err = -EIO; goto out_err; } app = g_try_malloc0(sizeof(struct desfire_app)); if (!app) { err = -ENOMEM; goto out_err; } memcpy(app->aid, desfire_aid_1, ARRAY_SIZE(desfire_aid_1)); app->key_settings = DESFIRE_KEY_SETTINGS; app->number_of_keys = DESFIRE_NUM_OF_KEYS; memcpy(app->file_id, desfire_file_id, ARRAY_SIZE(desfire_file_id)); memcpy(app->iso_appname, iso_appname_v2, ARRAY_SIZE(iso_appname_v2)); /* Step3 : Create Application */ err = ISO_send_cmd(PICC_CLASS, CREATE_APPLICATION, 0x00, 0x00, (uint8_t *)app, sizeof(struct desfire_app), true, select_application_1, cookie); if (err < 0) { near_error("create application req failed %d", err); goto out_err; } g_free(app); return 0; out_err: g_free(app); return t4_cookie_release(err, cookie); } static int select_application(uint8_t *resp, int length, void *data) { int err; struct t4_cookie *cookie = data; uint8_t *cmd_data = NULL; uint8_t cmd_data_length; uint8_t desfire_aid[] = {0x00, 0x00, 0x00}; /* 000000h */ DBG(""); if (length < 0) { near_error("get version3 resp failed %d", length); err = length; goto out_err; } if (resp[length - 1] != 0x00) { near_error("get version3 response %02X", resp[length - 1]); err = -EIO; goto out_err; } /* AID : 000000h */ cmd_data_length = ARRAY_SIZE(desfire_aid); cmd_data = g_try_malloc0(cmd_data_length); if (!cmd_data) { err = -ENOMEM; goto out_err; } memcpy(cmd_data, desfire_aid, cmd_data_length); /* Step2 : Select Application */ err = ISO_send_cmd(PICC_CLASS, SELECT_APPLICATION, 0x00, 0x00, cmd_data, cmd_data_length, true, create_application, cookie); if (err < 0) { near_error("select application req failed %d", err); goto out_err; } g_free(cmd_data); return 0; out_err: g_free(cmd_data); return t4_cookie_release(err, cookie); } static int get_version_frame3(uint8_t *resp, int length, void *data) { int err; struct t4_cookie *cookie = data; DBG(""); if (length < 0) { near_error("get version2 resp failed %d", length); return t4_cookie_release(length, cookie); } if (resp[4] == 0x01 /* Major Version */ && resp[length - 1] == GET_VERSION_FRAME_RESPONSE_BYTE) { err = ISO_send_cmd(PICC_CLASS, GET_VERSION_FRAME_RESPONSE_BYTE, 0x00, 0x00, NULL, 0, false, select_application, cookie); if (err < 0) { near_error("get version3 req failed %d", err); return t4_cookie_release(err, cookie); } } else { near_error("get version2 response %02X", resp[length - 1]); return t4_cookie_release(-EIO, cookie); } return 0; } static int get_version_frame2(uint8_t *resp, int length, void *data) { int err; struct t4_cookie *cookie = data; DBG(""); if (length < 0) { near_error(" get version resp failed %d", length); return t4_cookie_release(length, cookie); } if (resp[4] == 0x01 /* Major Version */ && resp[length - 1] == GET_VERSION_FRAME_RESPONSE_BYTE) { /* * When N is the GET_VERSION response 6th byte, * the DESFire tag memory size is 2 ^ (N /2). */ cookie->memory_size = (1 << (resp[6] / 2)); err = ISO_send_cmd(PICC_CLASS, GET_VERSION_FRAME_RESPONSE_BYTE, 0x00, 0x00, NULL, 0, false, get_version_frame3, cookie); if (err < 0) { near_error("get version2 req failed %d", err); return t4_cookie_release(err, cookie); } } else { near_error("get version response %02X", resp[length - 1]); return t4_cookie_release(-EIO, cookie); } return 0; } /* Steps to format Type 4 (MIFARE DESFire EV1) tag as per AN1104.pdf from nxp. * 1) Get version to determine memory size of tag * 2) Select applciation with AID equal to 000000h (PICC level) * 3) Create application with AID equal to 000001h * 4) Select application (Select previously created application in step3) * 5) Create std data file with File number equal to 01h (CC file), ISOFileID * equal to E103h, ComSet equal to 00h, AccesRights to EEEEh, FileSize bigger * equal to 00000Fh * 6) Write data to CC file with CCLEN equal to 000Fh, Mapping version equal to * 20h, MLe equal to 003Bh, MLc equal to 0034h, and NDEF File control TLV * equal to: T=04h, L=06h, V=E1 04 (NDEF ISO FID = E104h), 08 00 (NDEF File * size = 2048 Bytes) 00 (free read access) 00 (free write access) * 7) Create std data file with File number equal to 02h (NDEF File DESFireFId), * ISO FileID equal to E104h, ComSet equal to 00h, ComSet equal to 00h, * AccessRights equal to EEE0h, FileSize equal to 000800h (2048 bytes) * 8) Write data to write content of the NDEF File with NLEN equal to 0000h, and * no NDEF messsage. * 9) Now Formatting is done, then select ISO appname2, select CC file and read. * 10) Select NDEF file (by doing last two steps means, making sure that read * write operations perform on NDEF file). * */ static int nfctype4_format(uint32_t adapter_idx, uint32_t target_idx, near_tag_io_cb cb) { int err; struct t4_cookie *cookie; DBG(""); cookie = g_try_malloc0(sizeof(struct t4_cookie)); if (!cookie) return -ENOMEM; cookie->adapter_idx = adapter_idx; cookie->target_idx = target_idx; cookie->cb = cb; /* Step1 : Get Version */ err = ISO_send_cmd(PICC_CLASS, GET_VERSION, 0x00, 0x00, NULL, 0, false, get_version_frame2, cookie); if (err < 0) { near_error("get version req failed %d", err); g_free(cookie); } return err; } static struct near_tag_driver type4a_driver = { .type = NFC_PROTO_ISO14443, .priority = NEAR_TAG_PRIORITY_DEFAULT, .read = nfctype4_read, .write = nfctype4_write, .check_presence = nfctype4_check_presence, .format = nfctype4_format, }; static struct near_tag_driver type4b_driver = { .type = NFC_PROTO_ISO14443_B, .priority = NEAR_TAG_PRIORITY_DEFAULT, .read = nfctype4_read, .write = nfctype4_write, .check_presence = nfctype4_check_presence, .format = nfctype4_format, }; static int nfctype4_init(void) { int ret; DBG(""); ret = near_tag_driver_register(&type4b_driver); if (ret < 0) return ret; return near_tag_driver_register(&type4a_driver); } static void nfctype4_exit(void) { DBG(""); near_tag_driver_unregister(&type4a_driver); near_tag_driver_unregister(&type4b_driver); } NEAR_PLUGIN_DEFINE(nfctype4, "NFC Forum Type 4 tags support", VERSION, NEAR_PLUGIN_PRIORITY_HIGH, nfctype4_init, nfctype4_exit)