Add support for MIFARE UltraLight Tags.

Many thanks to Johann Dantant from SpringCard for giving me UltraLight cards.

While here, fix a few other problems in the autostuff.
This commit is contained in:
Romain Tartiere 2010-01-19 23:14:25 +00:00
parent cdf4404dd9
commit ef081454c7
8 changed files with 544 additions and 7 deletions

View file

@ -4,7 +4,10 @@ AM_LDFLAGS = @LIBNFC_LIBS@
lib_LTLIBRARIES = libfreefare.la
libfreefare_la_SOURCES = mifare_classic.c mad.c mifare_application.c
libfreefare_la_SOURCES = mifare_classic.c \
mifare_ultralight.c \
mad.c \
mifare_application.c
libfreefare_la_HEADERS = freefare.h
libfreefare_ladir = $(includedir)

View file

@ -30,6 +30,21 @@
extern "C" {
#endif // __cplusplus
struct mifare_ultralight_tag;
typedef struct mifare_ultralight_tag *MifareUltralightTag;
typedef uint8_t MifareUltralightPageNumber;
typedef unsigned char MifareUltralightPage[4];
MifareUltralightTag *mifare_ultralight_get_tags (nfc_device_t *device);
void mifare_ultralight_free_tags (MifareUltralightTag *tags);
int mifare_ultralight_connect (MifareUltralightTag tag);
int mifare_ultralight_disconnect (MifareUltralightTag tag);
int mifare_ultralight_read (MifareUltralightTag tag, const MifareUltralightPageNumber page, MifareUltralightPage *data);
int mifare_ultralight_write (MifareUltralightTag tag, const MifareUltralightPageNumber page, const MifareUltralightPage data);
char *mifare_ultralight_get_uid (MifareUltralightTag tag);
struct mifare_classic_tag;
typedef struct mifare_classic_tag *MifareClassicTag;

View file

@ -27,4 +27,19 @@ void crc8 (uint8_t *crc, const uint8_t value);
uint8_t sector_0x00_crc8 (Mad mad);
uint8_t sector_0x10_crc8 (Mad mad);
#define MIFARE_ULTRALIGHT_PAGE_COUNT 16
struct mifare_ultralight_tag {
nfc_device_t *device;
nfc_iso14443a_info_t info;
int active;
/* mifare_ultralight_read() reads 4 pages at a time (wrapping) */
MifareUltralightPage cache[MIFARE_ULTRALIGHT_PAGE_COUNT + 3];
uint8_t cached_pages[MIFARE_ULTRALIGHT_PAGE_COUNT];
};
#define ASSERT_ACTIVE(tag) do { if (!tag->active) return errno = ENXIO, -1; } while (0)
#define ASSERT_INACTIVE(tag) do { if (tag->active) return errno = ENXIO, -1; } while (0)
#endif /* !__FREEFARE_INTERNAL_H__ */

View file

@ -0,0 +1,260 @@
/*-
* Copyright (C) 2010, Romain Tartiere.
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* 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 Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
* $Id$
*/
/*
* This implementation was written based on information provided by the
* following documents:
*
* Contactless Single-trip Ticket IC
* MF0 IC U1
* Functional Specification
* Revision 3.0
* March 2003
*/
#include "config.h"
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <nfc/nfc.h>
#include <freefare.h>
#include "freefare_internal.h"
#define ASSERT_VALID_PAGE(page) do { if (page >= MIFARE_ULTRALIGHT_PAGE_COUNT) return errno = EINVAL, -1; } while (0)
/*
* MIFARE card communication preparation functions
*
* The following functions send NFC commands to the initiator to prepare
* communication with a MIFARE card, and perform required cleannups after using
* the target.
*/
/*
* Get a list of the MIFARE card near to the provided NFC initiator.
*
* The list can be freed using the mifare_ultralight_free_tags() function.
*/
MifareUltralightTag *
mifare_ultralight_get_tags (nfc_device_t *device)
{
MifareUltralightTag *tags = NULL;
int tag_count = 0;
nfc_initiator_init(device);
// Drop the field for a while
nfc_configure(device,NDO_ACTIVATE_FIELD,false);
// Let the reader only try once to find a tag
nfc_configure(device,NDO_INFINITE_SELECT,false);
// Configure the CRC and Parity settings
nfc_configure(device,NDO_HANDLE_CRC,true);
nfc_configure(device,NDO_HANDLE_PARITY,true);
// Enable field so more power consuming cards can power themselves up
nfc_configure(device,NDO_ACTIVATE_FIELD,true);
// Poll for a ISO14443A (MIFARE) tag
nfc_target_info_t target_info;
tags = malloc(sizeof (void *));
if(!tags) return NULL;
tags[0] = NULL;
while (nfc_initiator_select_tag(device,NM_ISO14443A_106,NULL,0,&target_info)) {
// Ensure the target is a MIFARE UltraLight tag.
if (!((target_info.nai.abtAtqa[0] == 0x00) &&
(target_info.nai.abtAtqa[1] == 0x44) &&
(target_info.nai.btSak == 0x00))) /* NXP MIFARE UltraLight */
continue;
tag_count++;
/* (Re)Allocate memory for the found MIFARE UltraLight array */
MifareUltralightTag *p = realloc (tags, (tag_count) * sizeof (MifareUltralightTag) + sizeof (void *));
if (p)
tags = p;
else
return tags; // FAIL! Return what has been found so far.
/* Allocate memory for the found MIFARE UltraLight tag */
if (!(tags[tag_count-1] = malloc (sizeof (struct mifare_ultralight_tag)))) {
return tags; // FAIL! Return what has been found before.
}
(tags[tag_count-1])->device = device;
(tags[tag_count-1])->info = target_info.nai;
(tags[tag_count-1])->active = 0;
for (int i = 0; i < MIFARE_ULTRALIGHT_PAGE_COUNT; i++) {
tags[tag_count-1]->cached_pages[i] = 0;
}
tags[tag_count] = NULL;
nfc_initiator_deselect_tag (device);
}
return tags;
}
/*
* Free the provided tag list.
*/
void
mifare_ultralight_free_tags (MifareUltralightTag *tags)
{
if (tags) {
for (int i=0; tags[i]; i++) {
free (tags[i]);
}
free (tags);
}
}
/*
* Establish connection to the provided tag.
*/
int
mifare_ultralight_connect (MifareUltralightTag tag)
{
ASSERT_INACTIVE (tag);
nfc_target_info_t pnti;
if (nfc_initiator_select_tag (tag->device, NM_ISO14443A_106, tag->info.abtUid, 7, &pnti)) {
tag->active = 1;
} else {
errno = EIO;
return -1;
}
return 0;
}
/*
* Terminate connection with the provided tag.
*/
int
mifare_ultralight_disconnect (MifareUltralightTag tag)
{
ASSERT_ACTIVE (tag);
if (nfc_initiator_deselect_tag (tag->device)) {
tag->active = 0;
} else {
errno = EIO;
return -1;
}
return 0;
}
/*
* Card manipulation functions
*
* The following functions perform direct communication with the connected
* MIFARE UltraLight tag.
*/
/*
* Read data from the provided MIFARE tag.
*/
int
mifare_ultralight_read (MifareUltralightTag tag, MifareUltralightPageNumber page, MifareUltralightPage *data)
{
ASSERT_ACTIVE (tag);
ASSERT_VALID_PAGE (page);
if (!tag->cached_pages[page]) {
uint8_t cmd[2];
cmd[0] = 0x30;
cmd[1] = page;
size_t n;
if (!(nfc_initiator_transceive_dep_bytes (tag->device, cmd, sizeof (cmd), tag->cache[page], &n))) {
errno = EIO;
return -1;
}
/* Handle wrapped pages */
for (int i = MIFARE_ULTRALIGHT_PAGE_COUNT; i <= page + 3; i++) {
memcpy (tag->cache[i % MIFARE_ULTRALIGHT_PAGE_COUNT], tag->cache[i], sizeof (MifareUltralightPage));
}
/* Mark pages as cached */
for (int i = page; i <= page + 3; i++) {
tag->cached_pages[i % MIFARE_ULTRALIGHT_PAGE_COUNT] = 1;
}
}
memcpy (data, tag->cache[page], sizeof (*data));
return 0;
}
/*
* Read data to the provided MIFARE tag.
*/
int
mifare_ultralight_write (MifareUltralightTag tag, const MifareUltralightPageNumber page, const MifareUltralightPage data)
{
ASSERT_ACTIVE (tag);
ASSERT_VALID_PAGE (page);
uint8_t cmd[6];
cmd[0] = 0xA2;
cmd[1] = page;
memcpy (cmd + 2, data, sizeof (MifareUltralightPage));
size_t n;
if (!(nfc_initiator_transceive_dep_bytes (tag->device, cmd, sizeof (cmd), NULL, &n))) {
errno = EIO;
return -1;
}
/* Invalidate page in cache */
tag->cached_pages[page] = 0;
return 0;
}
/*
* Miscellaneous functions
*/
char *
mifare_ultralight_get_uid (MifareUltralightTag tag)
{
char *uid = malloc (2 * 7 + 1);
MifareUltralightPage p0, p1;
mifare_ultralight_read (tag, 0, &p0);
mifare_ultralight_read (tag, 1, &p1);
sprintf (uid, "%02x%02x%02x%02x%02x%02x%02x",
p0[0],
p0[1],
p0[2],
p1[0],
p1[1],
p1[2],
p1[3]);
return uid;
}

View file

@ -15,24 +15,33 @@ noinst_LTLIBRARIES = \
test_mad.la \
test_mifare_classic.la \
test_mifare_classic_create_trailer_block.la \
test_mifare_classic_application.la
test_mifare_classic_application.la \
test_mifare_ultralight.la
AM_LDFLAGS = -module -rpath $(libdir) -avoid-version -no-undefined
test_mad_la_SOURCES = test_mad.c
test_mad_la_LIBADD = $(top_srcdir)/libfreefare/libfreefare.la
test_mad_la_LIBADD = $(top_builddir)/libfreefare/libfreefare.la
test_mifare_classic_la_SOURCES = test_mifare_classic.c \
mifare_classic_fixture.c
test_mifare_classic_la_LIBADD = $(top_srcdir)/libfreefare/libfreefare.la
mifare_classic_fixture.c \
mifare_classic_fixture.h
test_mifare_classic_la_LIBADD = $(top_builddir)/libfreefare/libfreefare.la
test_mifare_classic_create_trailer_block_la_SOURCES = test_mifare_classic_create_trailer_block.c
test_mifare_classic_create_trailer_block_la_LIBADD = $(top_srcdir)/libfreefare/libfreefare.la
test_mifare_classic_create_trailer_block_la_LIBADD = $(top_builddir)/libfreefare/libfreefare.la
test_mifare_classic_application_la_SOURCES = test_mifare_classic_application.c
test_mifare_classic_application_la_LIBADD = $(top_srcdir)/libfreefare/libfreefare.la
test_mifare_classic_application_la_LIBADD = $(top_builddir)/libfreefare/libfreefare.la
test_mifare_ultralight_la_SOURCES = test_mifare_ultralight.c \
mifare_ultralight_fixture.c \
mifare_ultralight_fixture.h
test_mifare_ultralight_la_LIBADD = $(top_builddir)/libfreefare/libfreefare.la
echo-cutter:
@echo $(CUTTER)
EXTRA_DIST = run-test.sh
endif

View file

@ -0,0 +1,58 @@
/*-
* Copyright (C) 2010, Romain Tartiere.
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* 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 Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
* $Id$
*/
#include <cutter.h>
#include <freefare.h>
static nfc_device_t *device = NULL;
static MifareUltralightTag *tags = NULL;
MifareUltralightTag tag = NULL;
void
setup ()
{
int res;
device = nfc_connect (NULL);
cut_assert_not_null (device, "No device found");
tags = mifare_ultralight_get_tags (device);
cut_assert_not_null (tags ,"Error enumerating NFC tags");
cut_assert_not_null (tags[0], "No MIFARE CLassic tag on NFC device");
tag = tags[0];
res = mifare_ultralight_connect (tag);
cut_assert_equal_int (0, res);
}
void
teardown ()
{
if (tag)
mifare_ultralight_disconnect (tag);
if (tags)
mifare_ultralight_free_tags (tags);
if (device)
nfc_disconnect (device);
}

View file

@ -0,0 +1,20 @@
/*-
* Copyright (C) 2010, Romain Tartiere.
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* 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 Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
* $Id$
*/
extern MifareUltralightTag tag;

View file

@ -0,0 +1,157 @@
/*-
* Copyright (C) 2010, Romain Tartiere.
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* 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 Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
* $Id$
*/
#include <cutter.h>
#include <errno.h>
#include <string.h>
#include <sys/limits.h>
#include <freefare.h>
#include "freefare_internal.h"
#include "mifare_ultralight_fixture.h"
void
test_mifare_ultralight_write (void)
{
int res;
MifareUltralightPage initial;
MifareUltralightPage page;
MifareUltralightPage payload1 = { 0x12, 0x34, 0x56, 0x78 };
MifareUltralightPage payload2 = { 0xaa, 0x55, 0x00, 0xff };
MifareUltralightPageNumber n = 7;
/* Read and save current value (should be { 0x00 0x00 0x00 0x00 }) */
res = mifare_ultralight_read (tag, n, &initial);
cut_assert_equal_int (0, res, "mifare_ultralight_read failed");
/* Write payload1 */
res = mifare_ultralight_write (tag, n, payload1);
cut_assert_equal_int (0, res, "mifare_ultralight_write failed");
/* Check it */
res = mifare_ultralight_read (tag, n, &page);
cut_assert_equal_int (0, res, "mifare_ultralight_read failed");
cut_assert_equal_memory (payload1, sizeof (payload1), page, sizeof (page));
/* Write payload2 */
res = mifare_ultralight_write (tag, n, payload2);
cut_assert_equal_int (0, res, "mifare_ultralight_write failed");
/* Check it */
res = mifare_ultralight_read (tag, n, &page);
cut_assert_equal_int (0, res, "mifare_ultralight_read failed");
cut_assert_equal_memory (payload2, sizeof (payload2), page, sizeof (page));
/* Write initial data */
res = mifare_ultralight_write (tag, n, initial);
cut_assert_equal_int (0, res, "mifare_ultralight_write failed");
/* While here check it (no reason to fail since the rest of the test passed) */
res = mifare_ultralight_read (tag, n, &page);
cut_assert_equal_int (0, res, "mifare_ultralight_read failed");
cut_assert_equal_memory (initial, sizeof (initial), page, sizeof (page));
}
void
test_mifare_ultralight_invalid_page (void)
{
int res;
MifareUltralightPage page = { 0x00, 0x00, 0x00, 0x00 };
res = mifare_ultralight_read (tag, 16, &page);
cut_assert_equal_int (-1, res);
cut_assert_equal_int (EINVAL, errno);
res = mifare_ultralight_write (tag, 16, page);
cut_assert_equal_int (-1, res);
cut_assert_equal_int (EINVAL, errno);
}
void
test_mifare_ultralight_cache (void)
{
int res;
MifareUltralightPage page;
res = mifare_ultralight_read (tag, 0, &page);
cut_assert_equal_int (0, res, "mifare_ultralight_read() failed");
/* Check cached pages consistency */
for (int i = 0; i <= 3; i++) {
cut_assert_equal_int (1, tag->cached_pages[i], cut_message ("Wrong page cache value for tag->cached_pages[%d]", i));
}
for (int i = 4; i < MIFARE_ULTRALIGHT_PAGE_COUNT; i++) {
cut_assert_equal_int (0, tag->cached_pages[i], cut_message ("Wrong page cache value for tag->cached_pages[%d]", i));
}
}
void
test_mifare_ultralight_cache_hit (void)
{
int res;
MifareUltralightPage page1;
MifareUltralightPage page2;
res = mifare_ultralight_read (tag, 0, &page1);
cut_assert_equal_int (0, res, "mifare_ultralight_read() failed");
res = mifare_ultralight_read (tag, 0, &page2);
cut_assert_equal_int (0, res, "mifare_ultralight_read() failed");
cut_assert_equal_memory (page1, sizeof (page1), page2, sizeof (page2));
}
void
test_mifare_ultralight_cache_wrap (void)
{
int res;
MifareUltralightPage page;
res = mifare_ultralight_read (tag, 15, &page);
cut_assert_equal_int (0, res, "mifare_ultralight_read() failed");
/* Check cached pages consistency */
for (int i = 0; i <= 2; i++) {
cut_assert_equal_int (1, tag->cached_pages[i], cut_message ("Wrong page cache value for tag->cached_pages[%d]", i));
}
for (int i = 3; i <= 14; i++) {
cut_assert_equal_int (0, tag->cached_pages[i], cut_message ("Wrong page cache value for tag->cached_pages[%d]", i));
}
for (int i = 15; i < MIFARE_ULTRALIGHT_PAGE_COUNT; i++) {
cut_assert_equal_int (1, tag->cached_pages[i], cut_message ("Wrong page cache value for tag->cached_pages[%d]", i));
}
}
void
test_mifare_ultralight_get_uid (void)
{
char *uid;
uid = mifare_ultralight_get_uid (tag);
cut_assert_not_null (uid);
cut_assert_equal_int (14, strlen (uid));
free (uid);
}