diff --git a/libfreefare/Makefile.am b/libfreefare/Makefile.am index 713dc05..af47a9d 100644 --- a/libfreefare/Makefile.am +++ b/libfreefare/Makefile.am @@ -151,6 +151,7 @@ linkedman = \ mifare_ultralight.3 mifare_ultralight_get_uid.3 \ mifare_ultralight.3 mifare_ultralight_read.3 \ mifare_ultralight.3 mifare_ultralight_write.3 \ + mifare_ultralight.3 mifare_ultralightc_authenticate.3 \ tlv.3 tlv_decode.3 \ tlv.3 tlv_encode.3 diff --git a/libfreefare/freefare.c b/libfreefare/freefare.c index f67608f..3d08466 100644 --- a/libfreefare/freefare.c +++ b/libfreefare/freefare.c @@ -29,11 +29,12 @@ #define NXP_MANUFACTURER_CODE 0x04 struct supported_tag supported_tags[] = { - { CLASSIC_1K, "Mifare Classic 1k", 0x08, 0, 0, { 0x00 } }, - { CLASSIC_4K, "Mifare Classic 4k", 0x18, 0, 0, { 0x00 } }, - { CLASSIC_4K, "Mifare Classic 4k (Emulated)", 0x38, 0, 0, { 0x00 } }, - { DESFIRE, "Mifare DESFire", 0x20, 5, 4, { 0x75, 0x77, 0x81, 0x02 /*, 0xXX */ }}, - { ULTRALIGHT, "Mifare UltraLight", 0x00, 0, 0, { 0x00 } }, + { CLASSIC_1K, "Mifare Classic 1k", 0x08, 0, 0, { 0x00 }, NULL }, + { CLASSIC_4K, "Mifare Classic 4k", 0x18, 0, 0, { 0x00 }, NULL }, + { CLASSIC_4K, "Mifare Classic 4k (Emulated)", 0x38, 0, 0, { 0x00 }, NULL }, + { DESFIRE, "Mifare DESFire", 0x20, 5, 4, { 0x75, 0x77, 0x81, 0x02 /*, 0xXX */ }, NULL}, + { ULTRALIGHT_C, "Mifare UltraLightC", 0x00, 0, 0, { 0x00 }, is_mifare_ultralightc_on_reader }, + { ULTRALIGHT, "Mifare UltraLight", 0x00, 0, 0, { 0x00 }, NULL }, }; /* @@ -51,7 +52,9 @@ freefare_tag_new (nfc_device_t *device, nfc_iso14443a_info_t nai) if (((nai.szUidLen == 4) || (nai.abtUid[0] == NXP_MANUFACTURER_CODE)) && (nai.btSak == supported_tags[i].SAK) && (!supported_tags[i].ATS_min_length || ((nai.szAtsLen >= supported_tags[i].ATS_min_length) && - (0 == memcmp (nai.abtAts, supported_tags[i].ATS, supported_tags[i].ATS_compare_length))))) { + (0 == memcmp (nai.abtAts, supported_tags[i].ATS, supported_tags[i].ATS_compare_length)) && + ((supported_tags[i].check_tag_on_reader == NULL) || + supported_tags[i].check_tag_on_reader(device, nai))))) { tag_info = &(supported_tags[i]); found = true; @@ -72,6 +75,7 @@ freefare_tag_new (nfc_device_t *device, nfc_iso14443a_info_t nai) tag = mifare_desfire_tag_new (); break; case ULTRALIGHT: + case ULTRALIGHT_C: tag = mifare_ultralight_tag_new (); break; } @@ -201,6 +205,7 @@ freefare_free_tag (MifareTag tag) mifare_desfire_tag_free (tag); break; case ULTRALIGHT: + case ULTRALIGHT_C: mifare_ultralight_tag_free (tag); break; } diff --git a/libfreefare/freefare.h b/libfreefare/freefare.h index 3c1e8c8..bc608ca 100644 --- a/libfreefare/freefare.h +++ b/libfreefare/freefare.h @@ -32,7 +32,7 @@ enum mifare_tag_type { ULTRALIGHT, -// ULTRALIGHT_C, + ULTRALIGHT_C, // MINI, CLASSIC_1K, CLASSIC_4K, @@ -46,6 +46,9 @@ enum mifare_tag_type { struct mifare_tag; typedef struct mifare_tag *MifareTag; +struct mifare_desfire_key; +typedef struct mifare_desfire_key *MifareDESFireKey; + typedef uint8_t MifareUltralightPageNumber; typedef unsigned char MifareUltralightPage[4]; @@ -66,6 +69,9 @@ int mifare_ultralight_disconnect (MifareTag tag); int mifare_ultralight_read (MifareTag tag, const MifareUltralightPageNumber page, MifareUltralightPage *data); int mifare_ultralight_write (MifareTag tag, const MifareUltralightPageNumber page, const MifareUltralightPage data); +int mifare_ultralightc_authenticate (MifareTag tag, const MifareDESFireKey key); +bool is_mifare_ultralightc_on_reader (nfc_device_t *device, nfc_iso14443a_info_t nai); + typedef unsigned char MifareClassicBlock[16]; typedef uint8_t MifareClassicSectorNumber; @@ -247,9 +253,6 @@ uint32_t mifare_desfire_aid_get_aid (MifareDESFireAID aid); uint8_t mifare_desfire_last_picc_error (MifareTag tag); -struct mifare_desfire_key; -typedef struct mifare_desfire_key *MifareDESFireKey; - #pragma pack (push) #pragma pack (1) struct mifare_desfire_version_info { diff --git a/libfreefare/freefare_internal.h b/libfreefare/freefare_internal.h index 59ce045..cd0984a 100644 --- a/libfreefare/freefare_internal.h +++ b/libfreefare/freefare_internal.h @@ -146,6 +146,7 @@ typedef enum { void *mifare_cryto_preprocess_data (MifareTag tag, void *data, size_t *nbytes, off_t offset, int communication_settings); void *mifare_cryto_postprocess_data (MifareTag tag, void *data, ssize_t *nbytes, int communication_settings); +void mifare_cypher_single_block (MifareDESFireKey key, uint8_t *data, uint8_t *ivect, MifareCryptoDirection direction, MifareCryptoOperation operation, size_t block_size); void mifare_cypher_blocks_chained (MifareTag tag, MifareDESFireKey key, uint8_t *ivect, uint8_t *data, size_t data_size, MifareCryptoDirection direction, MifareCryptoOperation operation); void rol (uint8_t *data, const size_t len); void desfire_crc32 (const uint8_t *data, const size_t len, uint8_t *crc); @@ -159,7 +160,11 @@ void cmac_generate_subkeys (MifareDESFireKey key); void cmac (const MifareDESFireKey key, uint8_t *ivect, const uint8_t *data, size_t len, uint8_t *cmac); void *assert_crypto_buffer_size (MifareTag tag, size_t nbytes); -#define MIFARE_ULTRALIGHT_PAGE_COUNT 16 +#define MIFARE_ULTRALIGHT_PAGE_COUNT 0x10 +#define MIFARE_ULTRALIGHT_C_PAGE_COUNT 0x30 +#define MIFARE_ULTRALIGHT_C_PAGE_COUNT_READ 0x2B +// Max PAGE_COUNT of the Ultralight Family: +#define MIFARE_ULTRALIGHT_MAX_PAGE_COUNT 0x30 struct supported_tag { enum mifare_tag_type type; @@ -168,6 +173,7 @@ struct supported_tag { uint8_t ATS_min_length; uint8_t ATS_compare_length; uint8_t ATS[5]; + bool (*check_tag_on_reader) (nfc_device_t *, nfc_iso14443a_info_t); }; /* @@ -245,8 +251,8 @@ struct mifare_ultralight_tag { struct mifare_tag __tag; /* 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]; + MifareUltralightPage cache[MIFARE_ULTRALIGHT_MAX_PAGE_COUNT + 3]; + uint8_t cached_pages[MIFARE_ULTRALIGHT_MAX_PAGE_COUNT]; }; /* @@ -260,7 +266,9 @@ struct mifare_ultralight_tag { #define ASSERT_MIFARE_CLASSIC(tag) do { if ((tag->tag_info->type != CLASSIC_1K) && (tag->tag_info->type != CLASSIC_4K)) return errno = ENODEV, -1; } while (0) #define ASSERT_MIFARE_DESFIRE(tag) do { if (tag->tag_info->type != DESFIRE) return errno = ENODEV, -1; } while (0) -#define ASSERT_MIFARE_ULTRALIGHT(tag) do { if (tag->tag_info->type != ULTRALIGHT) return errno = ENODEV, -1; } while (0) +#define IS_MIFARE_ULTRALIGHT_C(tag) (tag->tag_info->type == ULTRALIGHT_C) +#define ASSERT_MIFARE_ULTRALIGHT(tag) do { if ((tag->tag_info->type != ULTRALIGHT) && (! IS_MIFARE_ULTRALIGHT_C(tag))) return errno = ENODEV, -1; } while (0) +#define ASSERT_MIFARE_ULTRALIGHT_C(tag) do { if (! IS_MIFARE_ULTRALIGHT_C(tag)) return errno = ENODEV, -1; } while (0) /* * MifareTag cast macros diff --git a/libfreefare/mifare_desfire_crypto.c b/libfreefare/mifare_desfire_crypto.c index 7c05f1e..912ad10 100644 --- a/libfreefare/mifare_desfire_crypto.c +++ b/libfreefare/mifare_desfire_crypto.c @@ -71,7 +71,6 @@ #define CMAC_LENGTH 8 static void xor (const uint8_t *ivect, uint8_t *data, const size_t len); -static void mifare_cypher_single_block (MifareDESFireKey key, uint8_t *data, uint8_t *ivect, MifareCryptoDirection direction, MifareCryptoOperation operation, size_t block_size); static void desfire_crc32_byte (uint32_t *crc, const uint8_t value); static size_t key_macing_length (MifareDESFireKey key); @@ -608,7 +607,7 @@ mifare_cryto_postprocess_data (MifareTag tag, void *data, ssize_t *nbytes, int c return res; } -static void +void mifare_cypher_single_block (MifareDESFireKey key, uint8_t *data, uint8_t *ivect, MifareCryptoDirection direction, MifareCryptoOperation operation, size_t block_size) { AES_KEY k; diff --git a/libfreefare/mifare_ultralight.c b/libfreefare/mifare_ultralight.c index 9632d34..fea1406 100644 --- a/libfreefare/mifare_ultralight.c +++ b/libfreefare/mifare_ultralight.c @@ -45,7 +45,18 @@ #include #include "freefare_internal.h" -#define ASSERT_VALID_PAGE(page) do { if (page >= MIFARE_ULTRALIGHT_PAGE_COUNT) return errno = EINVAL, -1; } while (0) +#define ASSERT_VALID_PAGE(tag, page, mode_write) \ + do { \ + if (IS_MIFARE_ULTRALIGHT_C(tag)) { \ + if (mode_write) { \ + if (page >= MIFARE_ULTRALIGHT_C_PAGE_COUNT) return errno = EINVAL, -1; \ + } else { \ + if (page >= MIFARE_ULTRALIGHT_C_PAGE_COUNT_READ) return errno = EINVAL, -1; \ + } \ + } else { \ + if (page >= MIFARE_ULTRALIGHT_PAGE_COUNT) return errno = EINVAL, -1; \ + } \ + } while (0) #define ULTRALIGHT_TRANSCEIVE(tag, msg, res) \ do { \ @@ -57,6 +68,25 @@ DEBUG_XFER (res, __##res##_n, "<=== "); \ } while (0) +#define ULTRALIGHT_TRANSCEIVE_RAW(tag, msg, res) \ + do { \ + errno = 0; \ + if (!nfc_configure (tag->device, NDO_EASY_FRAMING, false)) { \ + errno = EIO; \ + return -1; \ + } \ + DEBUG_XFER (msg, __##msg##_n, "===> "); \ + if (!(nfc_initiator_transceive_bytes (tag->device, msg, __##msg##_n, res, &__##res##_n))) { \ + nfc_configure (tag->device, NDO_EASY_FRAMING, true); \ + return errno = EIO, -1; \ + } \ + DEBUG_XFER (res, __##res##_n, "<=== "); \ + if (!nfc_configure (tag->device, NDO_EASY_FRAMING, true)) { \ + errno = EIO; \ + return -1; \ + } \ + } while (0) + /* * Memory management functions. @@ -106,7 +136,7 @@ mifare_ultralight_connect (MifareTag tag) }; if (nfc_initiator_select_passive_target (tag->device, modulation, tag->info.abtUid, tag->info.szUidLen, &pnti)) { tag->active = 1; - for (int i = 0; i < MIFARE_ULTRALIGHT_PAGE_COUNT; i++) + for (int i = 0; i < MIFARE_ULTRALIGHT_MAX_PAGE_COUNT; i++) MIFARE_ULTRALIGHT(tag)->cached_pages[i] = 0; } else { errno = EIO; @@ -149,7 +179,7 @@ mifare_ultralight_read (MifareTag tag, MifareUltralightPageNumber page, MifareUl { ASSERT_ACTIVE (tag); ASSERT_MIFARE_ULTRALIGHT (tag); - ASSERT_VALID_PAGE (page); + ASSERT_VALID_PAGE (tag, page, false); if (!MIFARE_ULTRALIGHT(tag)->cached_pages[page]) { BUFFER_INIT (cmd, 2); @@ -161,13 +191,19 @@ mifare_ultralight_read (MifareTag tag, MifareUltralightPageNumber page, MifareUl ULTRALIGHT_TRANSCEIVE (tag, cmd, res); /* Handle wrapped pages */ - for (int i = MIFARE_ULTRALIGHT_PAGE_COUNT; i <= page + 3; i++) { - memcpy (MIFARE_ULTRALIGHT(tag)->cache[i % MIFARE_ULTRALIGHT_PAGE_COUNT], MIFARE_ULTRALIGHT(tag)->cache[i], sizeof (MifareUltralightPage)); + int iPageCount; + if (IS_MIFARE_ULTRALIGHT_C(tag)) { + iPageCount = MIFARE_ULTRALIGHT_C_PAGE_COUNT_READ; + } else { + iPageCount = MIFARE_ULTRALIGHT_PAGE_COUNT; + } + for (int i = iPageCount; i <= page + 3; i++) { + memcpy (MIFARE_ULTRALIGHT(tag)->cache[i % iPageCount], MIFARE_ULTRALIGHT(tag)->cache[i], sizeof (MifareUltralightPage)); } /* Mark pages as cached */ for (int i = page; i <= page + 3; i++) { - MIFARE_ULTRALIGHT(tag)->cached_pages[i % MIFARE_ULTRALIGHT_PAGE_COUNT] = 1; + MIFARE_ULTRALIGHT(tag)->cached_pages[i % iPageCount] = 1; } } @@ -183,7 +219,7 @@ mifare_ultralight_write (MifareTag tag, const MifareUltralightPageNumber page, c { ASSERT_ACTIVE (tag); ASSERT_MIFARE_ULTRALIGHT (tag); - ASSERT_VALID_PAGE (page); + ASSERT_VALID_PAGE (tag, page, true); uint8_t cmd[6]; cmd[0] = 0xA2; @@ -201,3 +237,96 @@ mifare_ultralight_write (MifareTag tag, const MifareUltralightPageNumber page, c return 0; } + +/* + * Authenticate to the provided MIFARE tag. + */ +int +mifare_ultralightc_authenticate (MifareTag tag, const MifareDESFireKey key) +{ + ASSERT_ACTIVE (tag); + ASSERT_MIFARE_ULTRALIGHT_C (tag); + + BUFFER_INIT (cmd1, 2); + BUFFER_INIT (res, 9); + BUFFER_APPEND (cmd1, 0x1A); + BUFFER_APPEND (cmd1, 0x00); + + ULTRALIGHT_TRANSCEIVE_RAW(tag, cmd1, res); + + uint8_t PICC_E_RndB[8]; + memcpy (PICC_E_RndB, res+1, 8); + + uint8_t PICC_RndB[8]; + memcpy (PICC_RndB, PICC_E_RndB, 8); + uint8_t ivect[8]; + memset (ivect, '\0', sizeof (ivect)); + mifare_cypher_single_block (key, PICC_RndB, ivect, MCD_RECEIVE, MCO_DECYPHER, 8); + + uint8_t PCD_RndA[8]; + DES_random_key ((DES_cblock*)&PCD_RndA); + + uint8_t PCD_r_RndB[8]; + memcpy (PCD_r_RndB, PICC_RndB, 8); + rol (PCD_r_RndB, 8); + + uint8_t token[16]; + memcpy (token, PCD_RndA, 8); + memcpy (token+8, PCD_r_RndB, 8); + size_t offset = 0; + + while (offset < 16) { + mifare_cypher_single_block (key, token + offset, ivect, MCD_SEND, MCO_ENCYPHER, 8); + offset += 8; + } + + BUFFER_INIT (cmd2, 17); + + BUFFER_APPEND (cmd2, 0xAF); + BUFFER_APPEND_BYTES (cmd2, token, 16); + + ULTRALIGHT_TRANSCEIVE_RAW(tag, cmd2, res); + + uint8_t PICC_E_RndA_s[8]; + memcpy (PICC_E_RndA_s, res+1, 8); + + uint8_t PICC_RndA_s[8]; + memcpy (PICC_RndA_s, PICC_E_RndA_s, 8); + mifare_cypher_single_block (key, PICC_RndA_s, ivect, MCD_RECEIVE, MCO_DECYPHER, 8); + + uint8_t PCD_RndA_s[8]; + memcpy (PCD_RndA_s, PCD_RndA, 8); + rol (PCD_RndA_s, 8); + + if (0 != memcmp (PCD_RndA_s, PICC_RndA_s, 8)) { + return -1; + } + // XXX Should we store the state "authenticated" in the tag struct?? + return 0; +} + +/* + * Callback for freefare_tag_new to test presence of a MIFARE UltralightC on the reader. + */ +bool +is_mifare_ultralightc_on_reader (nfc_device_t *device, nfc_iso14443a_info_t nai) +{ + bool ret; + uint8_t cmd_step1[2]; + uint8_t res_step1[9]; + cmd_step1[0] = 0x1A; + cmd_step1[1] = 0x00; + + nfc_target_t pnti; + nfc_modulation_t modulation = { + .nmt = NMT_ISO14443A, + .nbr = NBR_106 + }; + nfc_initiator_select_passive_target (device, modulation, nai.abtUid, nai.szUidLen, &pnti); + nfc_configure (device, NDO_EASY_FRAMING, false); + size_t n; + ret = nfc_initiator_transceive_bytes (device, cmd_step1, sizeof (cmd_step1), res_step1, &n); + nfc_configure (device, NDO_EASY_FRAMING, true); + nfc_initiator_deselect_target (device); + return ret; +} diff --git a/test/mifare_ultralight_fixture.c b/test/mifare_ultralight_fixture.c index 9b190b4..34af5d5 100644 --- a/test/mifare_ultralight_fixture.c +++ b/test/mifare_ultralight_fixture.c @@ -48,7 +48,8 @@ cut_setup () tag = NULL; for (int i=0; tags[i]; i++) { - if (freefare_get_tag_type(tags[i]) == ULTRALIGHT) { + if ((freefare_get_tag_type(tags[i]) == ULTRALIGHT) || + (freefare_get_tag_type(tags[i]) == ULTRALIGHT_C)) { tag = tags[i]; res = mifare_ultralight_connect (tag); cut_assert_equal_int (0, res, cut_message ("mifare_ultralight_connect() failed")); diff --git a/test/test_mifare_ultralight.c b/test/test_mifare_ultralight.c index e51b4fd..b9fb2b4 100644 --- a/test/test_mifare_ultralight.c +++ b/test/test_mifare_ultralight.c @@ -76,11 +76,17 @@ test_mifare_ultralight_invalid_page (void) int res; MifareUltralightPage page = { 0x00, 0x00, 0x00, 0x00 }; - res = mifare_ultralight_read (tag, 16, &page); + int invalid_page; + if (IS_MIFARE_ULTRALIGHT_C (tag)) { + invalid_page = MIFARE_ULTRALIGHT_C_PAGE_COUNT; + } else { + invalid_page = MIFARE_ULTRALIGHT_PAGE_COUNT; + } + res = mifare_ultralight_read (tag, invalid_page, &page); cut_assert_equal_int (-1, res, cut_message ("mifare_ultralight_read() succeeded")); cut_assert_equal_int (EINVAL, errno, cut_message ("Wrong errno value")); - res = mifare_ultralight_write (tag, 16, page); + res = mifare_ultralight_write (tag, invalid_page, page); cut_assert_equal_int (-1, res, cut_message ("mifare_ultralight_write() succeeded")); cut_assert_equal_int (EINVAL, errno, cut_message ("Wrong errno value")); } @@ -125,20 +131,26 @@ test_mifare_ultralight_cache_wrap (void) { int res; MifareUltralightPage page; - - res = mifare_ultralight_read (tag, 15, &page); + int last_page; + if (IS_MIFARE_ULTRALIGHT_C (tag)) { + // Last 4 blocks are for 3DES key and cannot be read, read will wrap from 0x2b + last_page = MIFARE_ULTRALIGHT_C_PAGE_COUNT_READ -1; + // Actually engineering samples require auth to read above page 0x28 so we skip the test entirely + cut_omit("mifare_ultralight_read() on last page skipped on UltralightC"); + } else { + last_page = MIFARE_ULTRALIGHT_PAGE_COUNT -1; + } + res = mifare_ultralight_read (tag, last_page, &page); cut_assert_equal_int (0, res, cut_message ("mifare_ultralight_read() failed")); /* Check cached pages consistency */ for (int i = 0; i <= 2; i++) { cut_assert_equal_int (1, MIFARE_ULTRALIGHT(tag)->cached_pages[i], cut_message ("Wrong page cache value for tag->cached_pages[%d]", i)); } - for (int i = 3; i <= 14; i++) { + for (int i = 3; i < last_page; i++) { cut_assert_equal_int (0, MIFARE_ULTRALIGHT(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, MIFARE_ULTRALIGHT(tag)->cached_pages[i], cut_message ("Wrong page cache value for tag->cached_pages[%d]", i)); - } + cut_assert_equal_int (1, MIFARE_ULTRALIGHT(tag)->cached_pages[last_page], cut_message ("Wrong page cache value for tag->cached_pages[%d]", last_page)); } void @@ -162,4 +174,24 @@ test_mifare_ultralight_tag_friendly_name (void) cut_assert_not_null (name, cut_message ("freefare_get_tag_friendly_name() failed")); } +void +test_mifare_ultralightc_authenticate (void) +{ + int res; + MifareDESFireKey key; + if (tag->tag_info->type == ULTRALIGHT_C) { + uint8_t key1_3des_data[16] = { 0x49, 0x45, 0x4D, 0x4B, 0x41, 0x45, 0x52, 0x42, 0x21, 0x4E, 0x41, 0x43, 0x55, 0x4F, 0X59, 0x46 }; + key = mifare_desfire_3des_key_new (key1_3des_data); + res = mifare_ultralightc_authenticate (tag, key); + cut_assert_equal_int (0, res, cut_message ("mifare_ultralightc_authenticate() failed")); + mifare_desfire_key_free (key); + + MifareUltralightPage page; + int last_page = MIFARE_ULTRALIGHT_C_PAGE_COUNT_READ -1; + res = mifare_ultralight_read (tag, last_page, &page); + cut_assert_equal_int (0, res, cut_message ("mifare_ultralight_read() failed")); + } else { + cut_omit("mifare_ultralightc_authenticate() skipped on Ultralight"); + } +}