Add support for MIFARE Classic 4K.
- New mifare_classic_first_sector_block(), mifare_classic_last_sector_block() functions to ease detection of sectors boundaries; - New unit tests for mifare_classic_first_sector_block() and mifare_classic_last_sector_block(); - Start to update the API for consistently using blocks and not mixing blocks and sectors with mifare_classic_*() functions; - Update the mifare-classic-format(1) example to handle MIFARE Classic 1k and 4k. Many thanks to Johann Dantant from SpringCard for giving me MIFARE Classic 4k cards.
This commit is contained in:
parent
24a9198f41
commit
5f7f8ffe2a
5 changed files with 151 additions and 57 deletions
|
@ -26,8 +26,6 @@
|
||||||
|
|
||||||
#include <freefare.h>
|
#include <freefare.h>
|
||||||
|
|
||||||
#define block_address(sector, block) ((sector * 4) + block)
|
|
||||||
|
|
||||||
MifareClassicKey default_keys[] = {
|
MifareClassicKey default_keys[] = {
|
||||||
{ 0xff,0xff,0xff,0xff,0xff,0xff },
|
{ 0xff,0xff,0xff,0xff,0xff,0xff },
|
||||||
{ 0xd3,0xf7,0xd3,0xf7,0xd3,0xf7 },
|
{ 0xd3,0xf7,0xd3,0xf7,0xd3,0xf7 },
|
||||||
|
@ -38,34 +36,61 @@ MifareClassicKey default_keys[] = {
|
||||||
{ 0xaa,0xbb,0xcc,0xdd,0xee,0xff },
|
{ 0xaa,0xbb,0xcc,0xdd,0xee,0xff },
|
||||||
{ 0x00,0x00,0x00,0x00,0x00,0x00 }
|
{ 0x00,0x00,0x00,0x00,0x00,0x00 }
|
||||||
};
|
};
|
||||||
|
int format_mifare_classic_1k (MifareTag tag);
|
||||||
|
int format_mifare_classic_4k (MifareTag tag);
|
||||||
|
int try_format_sector (MifareTag tag, MifareClassicBlockNumber block);
|
||||||
|
|
||||||
int
|
int
|
||||||
try_format_sector (MifareTag tag, MifareSectorNumber sector)
|
format_mifare_classic_1k (MifareTag tag)
|
||||||
|
{
|
||||||
|
for (int sector = 0; sector < 16; sector++) {
|
||||||
|
if (!try_format_sector (tag, sector * 4))
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
format_mifare_classic_4k (MifareTag tag)
|
||||||
|
{
|
||||||
|
for (int sector = 0; sector < 32; sector++) {
|
||||||
|
if (!try_format_sector (tag, sector * 4))
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
for (int sector = 0; sector < 8; sector++) {
|
||||||
|
if (!try_format_sector (tag, 128 + sector * 16))
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
try_format_sector (MifareTag tag, MifareClassicBlockNumber block)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < (sizeof (default_keys) / sizeof (MifareClassicKey)); i++) {
|
for (int i = 0; i < (sizeof (default_keys) / sizeof (MifareClassicKey)); i++) {
|
||||||
printf (" s=%d i=%d \n", sector, i);
|
if ((0 == mifare_classic_connect (tag)) && (0 == mifare_classic_authenticate (tag, block, default_keys[i], MFC_KEY_A))) {
|
||||||
if ((0 == mifare_classic_connect (tag)) && (0 == mifare_classic_authenticate (tag, block_address (sector, 0), default_keys[i], MFC_KEY_A))) {
|
if (0 == mifare_classic_format_sector (tag, block)) {
|
||||||
if (0 == mifare_classic_format_sector (tag, sector)) {
|
|
||||||
mifare_classic_disconnect (tag);
|
mifare_classic_disconnect (tag);
|
||||||
return 0;
|
return 1;
|
||||||
} else if (EIO == errno) {
|
} else if (EIO == errno) {
|
||||||
err (EXIT_FAILURE, "sector %d", sector);
|
err (EXIT_FAILURE, "block %d", block);
|
||||||
}
|
}
|
||||||
mifare_classic_disconnect (tag);
|
mifare_classic_disconnect (tag);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((0 == mifare_classic_connect (tag)) && (0 == mifare_classic_authenticate (tag, block_address (sector, 0), default_keys[i], MFC_KEY_B))) {
|
if ((0 == mifare_classic_connect (tag)) && (0 == mifare_classic_authenticate (tag, block, default_keys[i], MFC_KEY_B))) {
|
||||||
if (0 == mifare_classic_format_sector (tag, sector)) {
|
if (0 == mifare_classic_format_sector (tag, block)) {
|
||||||
mifare_classic_disconnect (tag);
|
mifare_classic_disconnect (tag);
|
||||||
return 0;
|
return 1;
|
||||||
} else if (EIO == errno) {
|
} else if (EIO == errno) {
|
||||||
err (EXIT_FAILURE, "sector %d", sector);
|
err (EXIT_FAILURE, "block %d", block);
|
||||||
}
|
}
|
||||||
mifare_classic_disconnect (tag);
|
mifare_classic_disconnect (tag);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return -1;
|
warnx ("No known authentication key for block %d", block);
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
|
@ -74,7 +99,6 @@ main(int argc, char *argv[])
|
||||||
int error = 0;
|
int error = 0;
|
||||||
nfc_device_t *device = NULL;
|
nfc_device_t *device = NULL;
|
||||||
MifareTag *tags = NULL;
|
MifareTag *tags = NULL;
|
||||||
MifareTag *tag = NULL;
|
|
||||||
|
|
||||||
device = nfc_connect (NULL);
|
device = nfc_connect (NULL);
|
||||||
if (!device)
|
if (!device)
|
||||||
|
@ -86,34 +110,39 @@ main(int argc, char *argv[])
|
||||||
errx (EXIT_FAILURE, "Error listing MIFARE classic tag.");
|
errx (EXIT_FAILURE, "Error listing MIFARE classic tag.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!tags[0]) {
|
for (int i = 0; (!error) && tags[i]; i++) {
|
||||||
freefare_free_tags (tags);
|
switch (freefare_get_tag_type (tags[i])) {
|
||||||
nfc_disconnect (device);
|
case CLASSIC_1K:
|
||||||
errx (EXIT_FAILURE, "No MIFARE tag on NFC device.");
|
case CLASSIC_4K:
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
tag = tags;
|
char *tag_uid = mifare_classic_get_uid (tags[i]);
|
||||||
|
char buffer[BUFSIZ];
|
||||||
|
|
||||||
if ((freefare_get_tag_type (*tag) != CLASSIC_1K) &&
|
printf ("Found MIFARE Classic %s. Format [yN] ", tag_uid);
|
||||||
(freefare_get_tag_type (*tag) != CLASSIC_4K)) {
|
fgets (buffer, BUFSIZ, stdin);
|
||||||
errx (EXIT_FAILURE, "Not a MIFARE Classic tag.");
|
bool format = ((buffer[0] == 'y') || (buffer[0] == 'Y'));
|
||||||
}
|
|
||||||
|
|
||||||
while (*tag) {
|
if (format) {
|
||||||
char *tag_uid = mifare_classic_get_uid (*tag);
|
switch (freefare_get_tag_type (tags[i])) {
|
||||||
|
case CLASSIC_1K:
|
||||||
/* FIXME get the tag size */
|
if (!format_mifare_classic_1k (tags[i]))
|
||||||
size_t sector_count = 15;
|
|
||||||
|
|
||||||
for (size_t n = 0; n < sector_count; n++) {
|
|
||||||
if (try_format_sector (*tag, n) < 0) {
|
|
||||||
warnx ("%s: Can't format sector %ld (0x%02lx)", tag_uid, n, n);
|
|
||||||
error = 1;
|
error = 1;
|
||||||
|
break;
|
||||||
|
case CLASSIC_4K:
|
||||||
|
if (!format_mifare_classic_4k (tags[i]))
|
||||||
|
error = 1;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
/* Keep compiler quiet */
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
free(tag_uid);
|
free (tag_uid);
|
||||||
tag++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
freefare_free_tags (tags);
|
freefare_free_tags (tags);
|
||||||
|
|
|
@ -88,7 +88,7 @@ int mifare_classic_transfer (MifareTag tag, const MifareClassicBlockNumber blo
|
||||||
int mifare_classic_get_trailer_block_permission (MifareTag tag, const MifareClassicBlockNumber block, const uint16_t permission, const MifareClassicKeyType key_type);
|
int mifare_classic_get_trailer_block_permission (MifareTag tag, const MifareClassicBlockNumber block, const uint16_t permission, const MifareClassicKeyType key_type);
|
||||||
int mifare_classic_get_data_block_permission (MifareTag tag, const MifareClassicBlockNumber block, const unsigned char permission, const MifareClassicKeyType key_type);
|
int mifare_classic_get_data_block_permission (MifareTag tag, const MifareClassicBlockNumber block, const unsigned char permission, const MifareClassicKeyType key_type);
|
||||||
|
|
||||||
int mifare_classic_format_sector (MifareTag tag, const MifareSectorNumber sector);
|
int mifare_classic_format_sector (MifareTag tag, const MifareClassicBlockNumber block);
|
||||||
char* mifare_classic_get_uid(MifareTag tag);
|
char* mifare_classic_get_uid(MifareTag tag);
|
||||||
|
|
||||||
void mifare_classic_trailer_block (MifareClassicBlock *block, const MifareClassicKey key_a, const uint8_t ab_0, const uint8_t ab_1, const uint8_t ab_2, const uint8_t ab_tb, const uint8_t gpb, const MifareClassicKey key_b);
|
void mifare_classic_trailer_block (MifareClassicBlock *block, const MifareClassicKey key_a, const uint8_t ab_0, const uint8_t ab_1, const uint8_t ab_2, const uint8_t ab_tb, const uint8_t gpb, const MifareClassicKey key_b);
|
||||||
|
|
|
@ -30,6 +30,8 @@ MifareTag mifare_ultralight_tag_new (void);
|
||||||
void mifare_ultralight_tag_free (MifareTag tag);
|
void mifare_ultralight_tag_free (MifareTag tag);
|
||||||
uint8_t sector_0x00_crc8 (Mad mad);
|
uint8_t sector_0x00_crc8 (Mad mad);
|
||||||
uint8_t sector_0x10_crc8 (Mad mad);
|
uint8_t sector_0x10_crc8 (Mad mad);
|
||||||
|
MifareClassicBlockNumber mifare_classic_first_sector_block (MifareClassicBlockNumber block);
|
||||||
|
MifareClassicBlockNumber mifare_classic_last_sector_block (MifareClassicBlockNumber block);
|
||||||
|
|
||||||
#define MIFARE_ULTRALIGHT_PAGE_COUNT 16
|
#define MIFARE_ULTRALIGHT_PAGE_COUNT 16
|
||||||
|
|
||||||
|
@ -83,7 +85,7 @@ struct mifare_ultralight_tag {
|
||||||
#define ASSERT_INACTIVE(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)
|
||||||
|
|
||||||
#define ASSERT_MIFARE_ULTRALIGHT(tag) do { if (tag->type != ULTRALIGHT) return errno = ENODEV, -1; } while (0)
|
#define ASSERT_MIFARE_ULTRALIGHT(tag) do { if (tag->type != ULTRALIGHT) return errno = ENODEV, -1; } while (0)
|
||||||
#define ASSERT_MIFARE_CLASSIC(tag) do { if (tag->type != CLASSIC_1K) return errno = ENODEV, -1; } while (0)
|
#define ASSERT_MIFARE_CLASSIC(tag) do { if ((tag->type != CLASSIC_1K) && (tag->type != CLASSIC_4K)) return errno = ENODEV, -1; } while (0)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* MifareTag cast macros
|
* MifareTag cast macros
|
||||||
|
|
|
@ -472,7 +472,7 @@ get_block_access_bits (MifareTag tag, const MifareClassicBlockNumber block, Mifa
|
||||||
|
|
||||||
uint16_t sector_access_bits, sector_access_bits_;
|
uint16_t sector_access_bits, sector_access_bits_;
|
||||||
|
|
||||||
MifareClassicBlockNumber trailer = ((block) / 4) * 4 + 3;
|
MifareClassicBlockNumber trailer = mifare_classic_last_sector_block (block);
|
||||||
|
|
||||||
if (MIFARE_CLASSIC(tag)->cached_access_bits.sector_trailer_block_number == trailer) {
|
if (MIFARE_CLASSIC(tag)->cached_access_bits.sector_trailer_block_number == trailer) {
|
||||||
/* cache hit! */
|
/* cache hit! */
|
||||||
|
@ -506,7 +506,7 @@ get_block_access_bits (MifareTag tag, const MifareClassicBlockNumber block, Mifa
|
||||||
* |,------C2
|
* |,------C2
|
||||||
* ||,---- C1
|
* ||,---- C1
|
||||||
* ||| */
|
* ||| */
|
||||||
uint16_t block_access_bits_mask = 0x0111 << (block % 4);
|
uint16_t block_access_bits_mask = 0x0111 << ((block == trailer) ? 3 : ((block < 128) ? block : ((block - 128) % 16) / 5) % 4);
|
||||||
/* |||
|
/* |||
|
||||||
* ||`---------------.
|
* ||`---------------.
|
||||||
* |`---------------.|
|
* |`---------------.|
|
||||||
|
@ -570,20 +570,29 @@ mifare_classic_get_data_block_permission (MifareTag tag, const MifareClassicBloc
|
||||||
* Reset a MIFARE target sector to factory default.
|
* Reset a MIFARE target sector to factory default.
|
||||||
*/
|
*/
|
||||||
int
|
int
|
||||||
mifare_classic_format_sector (MifareTag tag, const MifareSectorNumber sector)
|
mifare_classic_format_sector (MifareTag tag, const MifareClassicBlockNumber block)
|
||||||
{
|
{
|
||||||
MifareClassicBlockNumber first_sector_block = sector * 4;
|
MifareClassicBlockNumber first_sector_block = mifare_classic_first_sector_block (block);
|
||||||
|
MifareClassicBlockNumber last_sector_block = mifare_classic_last_sector_block (block);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Check that the current key allow us to rewrite data and trailer blocks.
|
* Check that the current key allow us to rewrite data and trailer blocks.
|
||||||
*/
|
*/
|
||||||
if (((sector != 0) && (mifare_classic_get_data_block_permission(tag, first_sector_block, MCAB_W, MIFARE_CLASSIC(tag)->last_authentication_key_type) != 1)) ||
|
|
||||||
(mifare_classic_get_data_block_permission(tag, first_sector_block + 1, MCAB_W, MIFARE_CLASSIC(tag)->last_authentication_key_type) != 1) ||
|
if (first_sector_block == 0) {
|
||||||
(mifare_classic_get_data_block_permission(tag, first_sector_block + 2, MCAB_W, MIFARE_CLASSIC(tag)->last_authentication_key_type) != 1) ||
|
/* First block is read-only */
|
||||||
(mifare_classic_get_trailer_block_permission(tag, first_sector_block + 3, MCAB_WRITE_KEYA, MIFARE_CLASSIC(tag)->last_authentication_key_type) != 1) ||
|
first_sector_block = 1;
|
||||||
(mifare_classic_get_trailer_block_permission(tag, first_sector_block + 3, MCAB_WRITE_ACCESS_BITS, MIFARE_CLASSIC(tag)->last_authentication_key_type) != 1) ||
|
}
|
||||||
(mifare_classic_get_trailer_block_permission(tag, first_sector_block + 3, MCAB_WRITE_KEYB, MIFARE_CLASSIC(tag)->last_authentication_key_type) != 1)) {
|
|
||||||
errno = EPERM;
|
for (int n = first_sector_block; n < last_sector_block; n++) {
|
||||||
return -1;
|
if (mifare_classic_get_data_block_permission(tag, n, MCAB_W, MIFARE_CLASSIC(tag)->last_authentication_key_type) != 1) {
|
||||||
|
return errno = EPERM, -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ((mifare_classic_get_trailer_block_permission(tag, last_sector_block, MCAB_WRITE_KEYA, MIFARE_CLASSIC(tag)->last_authentication_key_type) != 1) ||
|
||||||
|
(mifare_classic_get_trailer_block_permission(tag, last_sector_block, MCAB_WRITE_ACCESS_BITS, MIFARE_CLASSIC(tag)->last_authentication_key_type) != 1) ||
|
||||||
|
(mifare_classic_get_trailer_block_permission(tag, last_sector_block, MCAB_WRITE_KEYB, MIFARE_CLASSIC(tag)->last_authentication_key_type) != 1)) {
|
||||||
|
return errno = EPERM, -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
MifareClassicBlock empty_data_block;
|
MifareClassicBlock empty_data_block;
|
||||||
|
@ -596,12 +605,13 @@ mifare_classic_format_sector (MifareTag tag, const MifareSectorNumber sector)
|
||||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff /* Key B */
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff /* Key B */
|
||||||
};
|
};
|
||||||
|
|
||||||
if (((sector != 0) && (mifare_classic_write (tag, first_sector_block, empty_data_block) < 0)) ||
|
for (int n = first_sector_block; n < last_sector_block; n++) {
|
||||||
(mifare_classic_write (tag, first_sector_block + 1, empty_data_block) < 0) ||
|
if (mifare_classic_write (tag, n, empty_data_block) < 0) {
|
||||||
(mifare_classic_write (tag, first_sector_block + 2, empty_data_block) < 0) ||
|
return errno = EIO, -1;
|
||||||
(mifare_classic_write (tag, first_sector_block + 3, default_trailer_block) < 0)) {
|
}
|
||||||
errno = EIO;
|
}
|
||||||
return -1;
|
if (mifare_classic_write (tag, last_sector_block, default_trailer_block) < 0) {
|
||||||
|
return errno = EIO, -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -619,6 +629,39 @@ mifare_classic_get_uid(MifareTag tag)
|
||||||
return uid;
|
return uid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get the sector's first block number in the provided block's sector.
|
||||||
|
*/
|
||||||
|
MifareClassicBlockNumber
|
||||||
|
mifare_classic_first_sector_block (MifareClassicBlockNumber block)
|
||||||
|
{
|
||||||
|
int res;
|
||||||
|
if (block < 128) {
|
||||||
|
res = (block / 4) * 4;
|
||||||
|
} else {
|
||||||
|
res = ((block - 128) / 16) * 16 + 128;
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get the sector's last block number (aka trailer block) in the provided
|
||||||
|
* block's sector.
|
||||||
|
*/
|
||||||
|
MifareClassicBlockNumber
|
||||||
|
mifare_classic_last_sector_block (MifareClassicBlockNumber block)
|
||||||
|
{
|
||||||
|
int res;
|
||||||
|
if (block < 128) {
|
||||||
|
res = (block / 4) * 4 + 3;
|
||||||
|
} else {
|
||||||
|
res = ((block - 128) / 16) * 16 + 15 + 128;
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Generates a MIFARE trailer block.
|
* Generates a MIFARE trailer block.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#include <freefare.h>
|
#include <freefare.h>
|
||||||
|
#include "freefare_internal.h"
|
||||||
|
|
||||||
#include "mifare_classic_fixture.h"
|
#include "mifare_classic_fixture.h"
|
||||||
|
|
||||||
|
@ -337,3 +338,22 @@ test_mifare_classic_get_uid (void)
|
||||||
|
|
||||||
free (uid);
|
free (uid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
test_mifare_classic_sector_boundaries (void)
|
||||||
|
{
|
||||||
|
cut_notify ("No MIFARE Classic target is required for this test");
|
||||||
|
for (int i=0; i < 32; i++) {
|
||||||
|
for (int j=0; j < 4; j++) {
|
||||||
|
cut_assert_equal_int (4 * i, mifare_classic_first_sector_block (4 * i), cut_message ("Wrong first block number for block %d", i));
|
||||||
|
cut_assert_equal_int (4 * i + 3, mifare_classic_last_sector_block (4 * i + j), cut_message ("Wrong last block number for block %d", i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i=0; i < 8; i++) {
|
||||||
|
for (int j=0; j < 16; j++) {
|
||||||
|
cut_assert_equal_int (128 + 16 * i, mifare_classic_first_sector_block (128 + 16 * i), cut_message ("Wrong last block number for block %d", i));
|
||||||
|
cut_assert_equal_int (128 + 16 * i + 15, mifare_classic_last_sector_block (128 + 16 * i + j), cut_message ("Wrong last block number for block %d", i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue