mirror of
https://github.com/RfidResearchGroup/ChameleonMini.git
synced 2026-05-12 11:20:37 -07:00
748 lines
24 KiB
C
748 lines
24 KiB
C
#include "Reader14443A.h"
|
|
#include "Application.h"
|
|
#include "ISO14443-3A.h"
|
|
#include "../Codec/Reader14443-2A.h"
|
|
#include "Crypto1.h"
|
|
#include "../System.h"
|
|
|
|
#include "../Terminal/Terminal.h"
|
|
|
|
#define CHECK_BCC(B) ((B[0] ^ B[1] ^ B[2] ^ B[3]) == B[4])
|
|
#define IS_CASCADE_BIT_SET(buf) (buf[0] & 0x04)
|
|
#define IS_ISO14443A_4_COMPLIANT(buf) (buf[0] & 0x20)
|
|
|
|
// TODO replace remaining magic numbers
|
|
|
|
static bool Selected = false;
|
|
Reader14443Command Reader14443CurrentCommand = Reader14443_Do_Nothing;
|
|
|
|
static enum {
|
|
STATE_IDLE,
|
|
STATE_HALT,
|
|
STATE_READY,
|
|
STATE_ACTIVE_CL1, // must be ordered sequentially
|
|
STATE_ACTIVE_CL2,
|
|
STATE_ACTIVE_CL3,
|
|
STATE_SAK_CL1, // must be ordered sequentially
|
|
STATE_SAK_CL2,
|
|
STATE_SAK_CL3,
|
|
STATE_ATS,
|
|
STATE_DESELECT,
|
|
STATE_DESFIRE_INFO,
|
|
STATE_END
|
|
} ReaderState = STATE_IDLE;
|
|
|
|
static struct {
|
|
uint16_t ATQA;
|
|
uint8_t SAK;
|
|
uint8_t UID[10];
|
|
enum {
|
|
UIDSize_No_UID = 0,
|
|
UIDSize_Single = 4,
|
|
UIDSize_Double = 7,
|
|
UIDSize_Triple = 10
|
|
} UIDSize;
|
|
} CardCharacteristics = {0};
|
|
|
|
typedef enum {
|
|
CardType_NXP_MIFARE_Mini = 0, // do NOT assign another CardType item with a specific value since there are loops over this type
|
|
CardType_NXP_MIFARE_Classic_1k,
|
|
CardType_NXP_MIFARE_Classic_4k,
|
|
CardType_NXP_MIFARE_Ultralight,
|
|
CardType_NXP_MIFARE_DESFire,
|
|
CardType_NXP_MIFARE_DESFire_EV1,
|
|
CardType_IBM_JCOP31,
|
|
CardType_IBM_JCOP31_v241,
|
|
CardType_IBM_JCOP41_v22,
|
|
CardType_IBM_JCOP41_v231,
|
|
CardType_Infineon_MIFARE_Classic_1k,
|
|
CardType_Gemplus_MPCOS,
|
|
CardType_Innovision_Jewel,
|
|
CardType_Nokia_MIFARE_Classic_4k_emulated_6212,
|
|
CardType_Nokia_MIFARE_Classic_4k_emulated_6131
|
|
} CardType;
|
|
|
|
typedef struct {
|
|
uint16_t ATQA;
|
|
bool ATQARelevant;
|
|
|
|
uint8_t SAK;
|
|
bool SAKRelevant;
|
|
|
|
uint8_t ATS[16];
|
|
uint8_t ATSSize;
|
|
bool ATSRelevant;
|
|
|
|
char Manufacturer[16];
|
|
char Type[64];
|
|
} CardIdentificationType;
|
|
|
|
static const CardIdentificationType PROGMEM CardIdentificationList[] = {
|
|
[CardType_NXP_MIFARE_Mini] = { .ATQA=0x0004, .ATQARelevant=true, .SAK=0x09, .SAKRelevant=true, .ATSRelevant=false, .Manufacturer="NXP", .Type="MIFARE Mini" },
|
|
[CardType_NXP_MIFARE_Classic_1k] = { .ATQA=0x0004, .ATQARelevant=true, .SAK=0x08, .SAKRelevant=true, .ATSRelevant=false, .Manufacturer="NXP", .Type="MIFARE Classic 1k" },
|
|
[CardType_NXP_MIFARE_Classic_4k] = { .ATQA=0x0002, .ATQARelevant=true, .SAK=0x18, .SAKRelevant=true, .ATSRelevant=false, .Manufacturer="NXP", .Type="MIFARE Classic 4k" },
|
|
[CardType_NXP_MIFARE_Ultralight] = { .ATQA=0x0044, .ATQARelevant=true, .SAK=0x00, .SAKRelevant=true, .ATSRelevant=false, .Manufacturer="NXP", .Type="MIFARE Ultralight" },
|
|
// for the following two, setting ATSRelevant to true would cause checking the ATS value, but the NXP paper for distinguishing cards does not recommend this
|
|
[CardType_NXP_MIFARE_DESFire] = { .ATQA=0x0344, .ATQARelevant=true, .SAK=0x20, .SAKRelevant=true, .ATSRelevant=false, .ATSSize= 5, .ATS={0x75, 0x77, 0x81, 0x02, 0x80}, .Manufacturer="NXP", .Type="MIFARE DESFire" },
|
|
[CardType_NXP_MIFARE_DESFire_EV1] = { .ATQA=0x0344, .ATQARelevant=true, .SAK=0x20, .SAKRelevant=true, .ATSRelevant=false, .ATSSize= 5, .ATS={0x75, 0x77, 0x81, 0x02, 0x80}, .Manufacturer="NXP", .Type="MIFARE DESFire EV1" },
|
|
[CardType_IBM_JCOP31] = { .ATQA=0x0304, .ATQARelevant=true, .SAK=0x28, .SAKRelevant=true, .ATSRelevant=true, .ATSSize= 9, .ATS={0x38, 0x77, 0xb1, 0x4a, 0x43, 0x4f, 0x50, 0x33, 0x31}, .Manufacturer="IBM", .Type="JCOP31" },
|
|
[CardType_IBM_JCOP31_v241] = { .ATQA=0x0048, .ATQARelevant=true, .SAK=0x20, .SAKRelevant=true, .ATSRelevant=true, .ATSSize=12, .ATS={0x78, 0x77, 0xb1, 0x02, 0x4a, 0x43, 0x4f, 0x50, 0x76, 0x32, 0x34, 0x31}, .Manufacturer="IBM", .Type="JCOP31 v2.4.1" },
|
|
[CardType_IBM_JCOP41_v22] = { .ATQA=0x0048, .ATQARelevant=true, .SAK=0x20, .SAKRelevant=true, .ATSRelevant=true, .ATSSize=12, .ATS={0x38, 0x33, 0xb1, 0x4a, 0x43, 0x4f, 0x50, 0x34, 0x31, 0x56, 0x32, 0x32}, .Manufacturer="IBM", .Type="JCOP41 v2.2" },
|
|
[CardType_IBM_JCOP41_v231] = { .ATQA=0x0004, .ATQARelevant=true, .SAK=0x28, .SAKRelevant=true, .ATSRelevant=true, .ATSSize=13, .ATS={0x38, 0x33, 0xb1, 0x4a, 0x43, 0x4f, 0x50, 0x34, 0x31, 0x56, 0x32, 0x33, 0x31}, .Manufacturer="IBM", .Type="JCOP41 v2.3.1" },
|
|
[CardType_Infineon_MIFARE_Classic_1k] = { .ATQA=0x0004, .ATQARelevant=true, .SAK=0x88, .SAKRelevant=true, .ATSRelevant=false, .Manufacturer="Infineon", .Type="MIFARE Classic 1k" },
|
|
[CardType_Gemplus_MPCOS] = { .ATQA=0x0002, .ATQARelevant=true, .SAK=0x98, .SAKRelevant=true, .ATSRelevant=false, .Manufacturer="Gemplus", .Type="MPCOS" },
|
|
[CardType_Innovision_Jewel] = { .ATQA=0x0C00, .ATQARelevant=true, .SAKRelevant=false, .ATSRelevant=false, .Manufacturer="Innovision R&T", .Type="Jewel" },
|
|
[CardType_Nokia_MIFARE_Classic_4k_emulated_6212] = { .ATQA=0x0002, .ATQARelevant=true, .SAK=0x38, .SAKRelevant=true, .ATSRelevant=false, .Manufacturer="Nokia", .Type="MIFARE Classic 4k - emulated (6212 Classic)" },
|
|
[CardType_Nokia_MIFARE_Classic_4k_emulated_6131] = { .ATQA=0x0008, .ATQARelevant=true, .SAK=0x38, .SAKRelevant=true, .ATSRelevant=false, .Manufacturer="Nokia", .Type="MIFARE Classic 4k - emulated (6131 NFC)" }
|
|
};
|
|
|
|
static CardType CardCandidates[ARRAY_COUNT(CardIdentificationList)];
|
|
static uint8_t CardCandidatesIdx = 0;
|
|
|
|
uint16_t addParityBits(uint8_t * Buffer, uint16_t BitCount)
|
|
{
|
|
uint8_t i = CardIdentificationList[0].ATQA;
|
|
if (i == 0)
|
|
return 0;
|
|
if (BitCount == 7)
|
|
return 7;
|
|
if (BitCount % 8)
|
|
return BitCount;
|
|
uint8_t * currByte, * tmpByte;
|
|
uint8_t * const lastByte = Buffer + BitCount/8 + BitCount/64; // starting address + number of bytes + number of parity bytes
|
|
currByte = Buffer + BitCount/8 - 1;
|
|
uint8_t parity;
|
|
memset(currByte+1, 0, lastByte-currByte); // zeroize all bytes used for parity bits
|
|
while (currByte >= Buffer) // loop over all input bytes
|
|
{
|
|
parity = OddParityBit(*currByte); // get parity bit
|
|
tmpByte = lastByte;
|
|
while (tmpByte > currByte) // loop over all bytes from the last byte to the current one -- shifts the whole byte string
|
|
{
|
|
*tmpByte <<= 1; // shift this byte
|
|
*tmpByte |= (*(tmpByte-1) & 0x80) >> 7; // insert the last bit from the previous byte
|
|
tmpByte--; // go to the previous byte
|
|
}
|
|
*(++tmpByte) &= 0xFE; // zeroize the bit, where we want to put the parity bit
|
|
*tmpByte |= parity & 1; // add the parity bit
|
|
currByte--; // go to previous input byte
|
|
}
|
|
return BitCount + (BitCount / 8);
|
|
}
|
|
|
|
uint16_t removeParityBits(uint8_t * Buffer, uint16_t BitCount)
|
|
{
|
|
if (BitCount == 7)
|
|
return 7;
|
|
|
|
uint16_t i;
|
|
for (i = 0; i < (BitCount / 9); i++)
|
|
{
|
|
Buffer[i] = (Buffer[i + i/8] >> (i%8));
|
|
if (i%8)
|
|
Buffer[i] |= (Buffer[i + i/8 + 1] << (8 - (i % 8)));
|
|
}
|
|
return BitCount/9*8;
|
|
}
|
|
|
|
uint16_t removeSOC(uint8_t * Buffer, uint16_t BitCount)
|
|
{
|
|
if (BitCount == 0)
|
|
return 0;
|
|
uint16_t i;
|
|
Buffer[0] >>= 1;
|
|
for (i = 1; i < (BitCount + 7) / 8; i++)
|
|
{
|
|
Buffer[i-1] |= Buffer[i] << 7;
|
|
Buffer[i] >>= 1;
|
|
}
|
|
return BitCount - 1;
|
|
}
|
|
|
|
bool checkParityBits(uint8_t * Buffer, uint16_t BitCount)
|
|
{
|
|
if (BitCount == 7)
|
|
return true;
|
|
|
|
//if (BitCount % 9 || BitCount == 0)
|
|
// return false;
|
|
|
|
uint16_t i;
|
|
uint8_t currentByte, parity;
|
|
for (i = 0; i < (BitCount / 9); i++)
|
|
{
|
|
currentByte = (Buffer[i + i/8] >> (i%8));
|
|
if (i%8)
|
|
currentByte |= (Buffer[i + i/8 + 1] << (8 - (i % 8)));
|
|
parity = OddParityBit(currentByte);
|
|
if (((Buffer[i + i/8 + 1] >> (i % 8)) ^ parity) & 1) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void Reader14443AAppTimeout(void)
|
|
{
|
|
Reader14443AAppReset();
|
|
Reader14443ACodecReset();
|
|
ReaderState = STATE_IDLE;
|
|
}
|
|
|
|
void Reader14443AAppInit(void)
|
|
{
|
|
ReaderState = STATE_IDLE;
|
|
}
|
|
|
|
void Reader14443AAppReset(void)
|
|
{
|
|
ReaderState = STATE_IDLE;
|
|
Reader14443CurrentCommand = Reader14443_Do_Nothing;
|
|
Selected = false;
|
|
}
|
|
|
|
void Reader14443AAppTask(void)
|
|
{
|
|
|
|
}
|
|
|
|
void Reader14443AAppTick(void)
|
|
{
|
|
|
|
}
|
|
|
|
INLINE uint16_t Reader14443A_Deselect(uint8_t* Buffer) // deselects the card because of an error, so we will continue to select the card afterwards
|
|
{
|
|
Buffer[0] = 0xC2;
|
|
uint16_t crc = ISO14443_CRCA(Buffer, 1);
|
|
Buffer[1] = crc;
|
|
Buffer[2] = crc >> 8;
|
|
ReaderState = STATE_DESELECT;
|
|
Selected = false;
|
|
return addParityBits(Buffer, 24);
|
|
}
|
|
|
|
INLINE uint16_t Reader14443A_Select(uint8_t * Buffer, uint16_t BitCount)
|
|
{
|
|
if (Selected)
|
|
return 0;
|
|
switch (ReaderState)
|
|
{
|
|
case STATE_IDLE:
|
|
case STATE_HALT:
|
|
/* Send a REQA */
|
|
Buffer[0] = ISO14443A_CMD_WUPA; // whenever REQA works, WUPA also works, so we choose WUPA always
|
|
ReaderState = STATE_READY;
|
|
return 7;
|
|
|
|
case STATE_READY:
|
|
if (BitCount < 19)
|
|
{
|
|
ReaderState = STATE_IDLE;
|
|
Reader14443ACodecStart();
|
|
return 0;
|
|
}
|
|
BitCount = removeSOC(Buffer, BitCount);
|
|
if (!checkParityBits(Buffer, BitCount))
|
|
{
|
|
ReaderState = STATE_IDLE;
|
|
LogEntry(LOG_ERR_APP_CHECKSUM_FAIL, Buffer, (BitCount + 8) / 7);
|
|
Reader14443ACodecStart();
|
|
return 0;
|
|
}
|
|
BitCount = removeParityBits(Buffer, BitCount);
|
|
CardCharacteristics.ATQA = Buffer[1] << 8 | Buffer[0]; // save ATQA for possible later use
|
|
Buffer[0] = ISO14443A_CMD_SELECT_CL1;
|
|
Buffer[1] = 0x20; // NVB = 16
|
|
ReaderState = STATE_ACTIVE_CL1;
|
|
return addParityBits(Buffer, 16);
|
|
|
|
case STATE_ACTIVE_CL1 ... STATE_ACTIVE_CL3:
|
|
BitCount = removeSOC(Buffer, BitCount);
|
|
if (!checkParityBits(Buffer, BitCount) || BitCount < 8)
|
|
{
|
|
ReaderState = STATE_IDLE;
|
|
LogEntry(LOG_ERR_APP_CHECKSUM_FAIL, Buffer, (BitCount + 8) / 7);
|
|
Reader14443ACodecStart();
|
|
return 0;
|
|
}
|
|
BitCount = removeParityBits(Buffer, BitCount);
|
|
if (!CHECK_BCC(Buffer))
|
|
{
|
|
ReaderState = STATE_IDLE;
|
|
Reader14443ACodecStart();
|
|
return 0;
|
|
}
|
|
|
|
if (Buffer[0] == ISO14443A_UID0_CT)
|
|
{
|
|
memcpy(CardCharacteristics.UID + (ReaderState - STATE_ACTIVE_CL1) * 3, Buffer + 1, 3);
|
|
} else {
|
|
memcpy(CardCharacteristics.UID + (ReaderState - STATE_ACTIVE_CL1) * 3, Buffer, 4);
|
|
}
|
|
// shift received UID two bytes to the right
|
|
memmove(Buffer+2, Buffer, 5);
|
|
Buffer[0] = (ReaderState == STATE_ACTIVE_CL1) ? ISO14443A_CMD_SELECT_CL1 : (ReaderState == STATE_ACTIVE_CL2) ? ISO14443A_CMD_SELECT_CL2 : ISO14443A_CMD_SELECT_CL3;
|
|
Buffer[1] = 0x70; // NVB = 56
|
|
uint16_t crc = ISO14443_CRCA(Buffer, 7);
|
|
Buffer[7] = crc & 0xFF;
|
|
Buffer[8] = crc >> 8;
|
|
ReaderState = ReaderState - STATE_ACTIVE_CL1 + STATE_SAK_CL1;
|
|
return addParityBits(Buffer, 72);
|
|
|
|
case STATE_SAK_CL1 ... STATE_SAK_CL3:
|
|
if (BitCount < 9)
|
|
{
|
|
ReaderState = STATE_IDLE;
|
|
Reader14443ACodecStart();
|
|
return 0;
|
|
}
|
|
BitCount = removeSOC(Buffer, BitCount);
|
|
if (!checkParityBits(Buffer, BitCount))
|
|
{
|
|
LogEntry(LOG_ERR_APP_CHECKSUM_FAIL, Buffer, (BitCount + 8) / 7);
|
|
ReaderState = STATE_IDLE;
|
|
Reader14443ACodecStart();
|
|
return 0;
|
|
}
|
|
BitCount = removeParityBits(Buffer, BitCount);
|
|
if (ISO14443_CRCA(Buffer, 3))
|
|
{
|
|
ReaderState = STATE_IDLE;
|
|
Reader14443ACodecStart();
|
|
return 0;
|
|
}
|
|
if (IS_CASCADE_BIT_SET(Buffer) && ReaderState != STATE_SAK_CL3)
|
|
{
|
|
Buffer[0] = (ReaderState == STATE_SAK_CL1) ? ISO14443A_CMD_SELECT_CL2 : ISO14443A_CMD_SELECT_CL3;
|
|
Buffer[1] = 0x20; // NVB = 16 bit
|
|
ReaderState = ReaderState - STATE_SAK_CL1 + STATE_ACTIVE_CL1 + 1;
|
|
return addParityBits(Buffer, 16);
|
|
} else if (IS_CASCADE_BIT_SET(Buffer) && ReaderState == STATE_SAK_CL3) {
|
|
// TODO handle this very strange hopefully not happening error
|
|
}
|
|
Selected = true;
|
|
CardCharacteristics.UIDSize = (ReaderState - STATE_SAK_CL1) * 3 + 4;
|
|
CardCharacteristics.SAK = Buffer[0]; // save last SAK for possible later use
|
|
|
|
return 0;
|
|
|
|
case STATE_DESELECT:
|
|
if (BitCount == 0) // most likely the card already understood the deselect
|
|
{
|
|
ReaderState = STATE_HALT;
|
|
Reader14443ACodecStart();
|
|
return 0;
|
|
}
|
|
BitCount = removeSOC(Buffer, BitCount);
|
|
if (!checkParityBits(Buffer, BitCount))
|
|
{
|
|
LogEntry(LOG_ERR_APP_CHECKSUM_FAIL, Buffer, (BitCount + 8) / 7);
|
|
return Reader14443A_Deselect(Buffer);
|
|
}
|
|
BitCount = removeParityBits(Buffer, BitCount);
|
|
if (ISO14443_CRCA(Buffer, 3))
|
|
{
|
|
return Reader14443A_Deselect(Buffer);
|
|
}
|
|
ReaderState = STATE_HALT;
|
|
Reader14443ACodecStart();
|
|
return 0;
|
|
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
INLINE uint16_t Reader14443A_Halt(uint8_t* Buffer)
|
|
{
|
|
Buffer[0] = ISO14443A_CMD_HLTA;
|
|
Buffer[1] = 0x00;
|
|
uint16_t crc = ISO14443_CRCA(Buffer, 2);
|
|
Buffer[2] = crc;
|
|
Buffer[3] = crc >> 8;
|
|
ReaderState = STATE_HALT;
|
|
Selected = false;
|
|
return addParityBits(Buffer, 32);
|
|
}
|
|
|
|
INLINE uint16_t Reader14443A_RATS(uint8_t* Buffer)
|
|
{
|
|
Buffer[0] = 0xE0; // RATS command
|
|
Buffer[1] = 0x80;
|
|
uint16_t crc = ISO14443_CRCA(Buffer, 2);
|
|
Buffer[2] = crc;
|
|
Buffer[3] = crc >> 8;
|
|
ReaderState = STATE_ATS;
|
|
return addParityBits(Buffer, 32);
|
|
}
|
|
|
|
uint16_t Reader14443AAppProcess(uint8_t* Buffer, uint16_t BitCount)
|
|
{
|
|
switch (Reader14443CurrentCommand)
|
|
{
|
|
case Reader14443_Send:
|
|
{
|
|
if (ReaderSendBitCount)
|
|
{
|
|
memcpy(Buffer, ReaderSendBuffer, (ReaderSendBitCount + 7) / 8);
|
|
uint16_t tmp = addParityBits(Buffer, ReaderSendBitCount);
|
|
ReaderSendBitCount = 0;
|
|
return tmp;
|
|
}
|
|
|
|
if (BitCount == 0)
|
|
{
|
|
char tmpBuf[] = "NO DATA";
|
|
Reader14443CurrentCommand = Reader14443_Do_Nothing;
|
|
CommandLinePendingTaskFinished(COMMAND_INFO_OK_WITH_TEXT_ID, tmpBuf);
|
|
return 0;
|
|
}
|
|
char tmpBuf[128];
|
|
BitCount = removeSOC(Buffer, BitCount);
|
|
bool parity = checkParityBits(Buffer, BitCount);
|
|
BitCount = removeParityBits(Buffer, BitCount);
|
|
if ((2 * (BitCount + 7) / 8 + 2 + 4) > 128) // 2 = \r\n, 4 = size of bitcount in hex
|
|
{
|
|
sprintf(tmpBuf, "Too many data.");
|
|
Reader14443CurrentCommand = Reader14443_Do_Nothing;
|
|
CommandLinePendingTaskFinished(COMMAND_INFO_OK_WITH_TEXT_ID, tmpBuf);
|
|
return 0;
|
|
}
|
|
uint16_t charCnt = BufferToHexString(tmpBuf, 128, Buffer, (BitCount + 7) / 8);
|
|
uint8_t count[2] = {(BitCount>>8)&0xFF, BitCount&0xFF};
|
|
charCnt += snprintf(tmpBuf + charCnt, 128 - charCnt, "\r\n");
|
|
charCnt += BufferToHexString(tmpBuf + charCnt, 128 - charCnt, count, 2);
|
|
if (!parity)
|
|
snprintf(tmpBuf + charCnt, 128 - charCnt, "\r\nPARITY ERROR");
|
|
else
|
|
snprintf(tmpBuf + charCnt, 128 - charCnt, "\r\nPARITY OK");
|
|
Reader14443CurrentCommand = Reader14443_Do_Nothing;
|
|
CommandLinePendingTaskFinished(COMMAND_INFO_OK_WITH_TEXT_ID, tmpBuf);
|
|
return 0;
|
|
}
|
|
|
|
|
|
case Reader14443_Send_Raw:
|
|
{
|
|
if (ReaderSendBitCount)
|
|
{
|
|
memcpy(Buffer, ReaderSendBuffer, (ReaderSendBitCount + 7) / 8);
|
|
uint16_t tmp = ReaderSendBitCount;
|
|
ReaderSendBitCount = 0;
|
|
return tmp;
|
|
}
|
|
|
|
if (BitCount == 0)
|
|
{
|
|
char tmpBuf[] = "NO DATA";
|
|
Reader14443CurrentCommand = Reader14443_Do_Nothing;
|
|
CommandLinePendingTaskFinished(COMMAND_INFO_OK_WITH_TEXT_ID, tmpBuf);
|
|
return 0;
|
|
}
|
|
|
|
char tmpBuf[128];
|
|
uint16_t charCnt = BufferToHexString(tmpBuf, 128, Buffer, (BitCount + 7) / 8);
|
|
uint8_t count[2] = {(BitCount>>8)&0xFF, BitCount&0xFF};
|
|
charCnt += snprintf(tmpBuf + charCnt, 128 - charCnt, "\r\n");
|
|
charCnt += BufferToHexString(tmpBuf + charCnt, 128 - charCnt, count, 2);
|
|
Reader14443CurrentCommand = Reader14443_Do_Nothing;
|
|
CommandLinePendingTaskFinished(COMMAND_INFO_OK_WITH_TEXT_ID, tmpBuf);
|
|
return 0;
|
|
}
|
|
|
|
case Reader14443_Get_UID:
|
|
{
|
|
uint16_t rVal = Reader14443A_Select(Buffer, BitCount);
|
|
if (Selected) // we are done finding the UID
|
|
{
|
|
char tmpBuf[20];
|
|
BufferToHexString(tmpBuf, 20, CardCharacteristics.UID, CardCharacteristics.UIDSize);
|
|
CommandLinePendingTaskFinished(COMMAND_INFO_OK_WITH_TEXT_ID, tmpBuf);
|
|
Selected = false;
|
|
Reader14443CurrentCommand = Reader14443_Do_Nothing;
|
|
CodecReaderFieldStop();
|
|
return 0;
|
|
}
|
|
return rVal;
|
|
}
|
|
|
|
case Reader14443_Read_MF_Ultralight:
|
|
{
|
|
static uint8_t MFURead_CurrentAdress = 0;
|
|
static uint8_t MFUContents[64];
|
|
|
|
uint16_t rVal = Reader14443A_Select(Buffer, BitCount);
|
|
if (Selected)
|
|
{
|
|
if (MFURead_CurrentAdress != 0)
|
|
{
|
|
BitCount = removeSOC(Buffer, BitCount);
|
|
if (BitCount == 0) // relaunch select protocol
|
|
{
|
|
MFURead_CurrentAdress = 0; // reset read address
|
|
Selected = false;
|
|
ReaderState = STATE_IDLE;
|
|
Reader14443ACodecStart();
|
|
return 0;
|
|
}
|
|
bool readPageAgain = (BitCount < 162) || !checkParityBits(Buffer, BitCount);
|
|
BitCount = removeParityBits(Buffer, BitCount);
|
|
if (readPageAgain || ISO14443_CRCA(Buffer, 18)) // the CRC function should return 0 if everything is ok
|
|
{
|
|
MFURead_CurrentAdress -= 4;
|
|
} else { // everything is ok for this page
|
|
memcpy(MFUContents + (MFURead_CurrentAdress - 4) * 4, Buffer, 16);
|
|
}
|
|
} else {
|
|
uint16_t RefATQA;
|
|
memcpy_P(&RefATQA, &CardIdentificationList[CardType_NXP_MIFARE_Ultralight].ATQA, 2);
|
|
uint8_t RefSAK = pgm_read_byte(&CardIdentificationList[CardType_NXP_MIFARE_Ultralight].SAK);
|
|
if (CardCharacteristics.ATQA != RefATQA || CardCharacteristics.SAK != RefSAK) // seems to be no MiFare Ultralight card, so retry
|
|
{
|
|
ReaderState = STATE_IDLE;
|
|
Reader14443ACodecStart();
|
|
return 0;
|
|
}
|
|
}
|
|
if (MFURead_CurrentAdress == 16)
|
|
{
|
|
Selected = false;
|
|
MFURead_CurrentAdress = 0;
|
|
Reader14443CurrentCommand = Reader14443_Do_Nothing;
|
|
|
|
char tmpBuf[135]; // 135 = 128 hex digits + 3 * \r\n + \0
|
|
BufferToHexString( tmpBuf, 135, MFUContents, 16);
|
|
snprintf( tmpBuf + 32, 135 - 32, "\r\n");
|
|
BufferToHexString( tmpBuf + 32 + 2, 135 - 32 - 2, MFUContents + 16, 16);
|
|
snprintf( tmpBuf + 32 + 2 + 32, 135 - 32 - 2 - 32, "\r\n");
|
|
BufferToHexString( tmpBuf + 32 + 2 + 32 + 2, 135 - 32 - 2 - 32 - 2, MFUContents + 32, 16);
|
|
snprintf( tmpBuf + 32 + 2 + 32 + 2 + 32, 135 - 32 - 2 - 32 - 2 - 32, "\r\n");
|
|
BufferToHexString( tmpBuf + 32 + 2 + 32 + 2 + 32 + 2, 135 - 32 - 2 - 32 - 2 - 32 - 2, MFUContents + 48, 16);
|
|
CodecReaderFieldStop();
|
|
CommandLinePendingTaskFinished(COMMAND_INFO_OK_WITH_TEXT_ID, tmpBuf);
|
|
return 0;
|
|
}
|
|
Buffer[0] = 0x30; // MiFare Ultralight read command
|
|
Buffer[1] = MFURead_CurrentAdress;
|
|
uint16_t crc = ISO14443_CRCA(Buffer, 2);
|
|
Buffer[2] = crc;
|
|
Buffer[3] = crc >> 8;
|
|
|
|
MFURead_CurrentAdress += 4;
|
|
|
|
return addParityBits(Buffer, 32);
|
|
}
|
|
return rVal;
|
|
}
|
|
|
|
/************************************
|
|
* This function identifies a PICC. *
|
|
************************************/
|
|
case Reader14443_Indentify:
|
|
{
|
|
uint16_t rVal = Reader14443A_Select(Buffer, BitCount);
|
|
if (Selected)
|
|
{
|
|
if (ReaderState >= STATE_SAK_CL1 && ReaderState <= STATE_SAK_CL3)
|
|
{
|
|
bool ISO14443_4A_compliant = IS_ISO14443A_4_COMPLIANT(Buffer);
|
|
|
|
uint8_t i;
|
|
for (i = 0; i < ARRAY_COUNT(CardIdentificationList); i++)
|
|
{
|
|
CardIdentificationType card;
|
|
memcpy_P(&card, &CardIdentificationList[i], sizeof(CardIdentificationType));
|
|
if (card.ATQARelevant && card.ATQA != CardCharacteristics.ATQA)
|
|
continue;
|
|
if (card.SAKRelevant && card.SAK != CardCharacteristics.SAK)
|
|
continue;
|
|
if (card.ATSRelevant && !ISO14443_4A_compliant)
|
|
continue; // for this card type candidate, the ATS is relevant, but the card does not support ISO14443-4A
|
|
CardCandidates[CardCandidatesIdx++] = i;
|
|
}
|
|
|
|
if (ISO14443_4A_compliant)
|
|
{
|
|
// send RATS
|
|
return Reader14443A_RATS(Buffer);
|
|
}
|
|
// if we don't have to send the RATS, we are finished for distinguishing with ISO 14443A
|
|
|
|
} else if (ReaderState == STATE_ATS) { // we have got the ATS
|
|
BitCount = removeSOC(Buffer, BitCount);
|
|
if (!checkParityBits(Buffer, BitCount))
|
|
{
|
|
LogEntry(LOG_ERR_APP_CHECKSUM_FAIL, Buffer, (BitCount + 8) / 7);
|
|
return Reader14443A_Deselect(Buffer);
|
|
}
|
|
BitCount = removeParityBits(Buffer, BitCount);
|
|
|
|
if (Buffer[0] != BitCount / 8 - 2 || ISO14443_CRCA(Buffer, Buffer[0] + 2))
|
|
{
|
|
return Reader14443A_Deselect(Buffer);
|
|
}
|
|
|
|
uint8_t i;
|
|
for (i = 0; i < CardCandidatesIdx; i++)
|
|
{
|
|
CardIdentificationType card;
|
|
memcpy_P(&card, &CardIdentificationList[CardCandidates[i]], sizeof(CardIdentificationType));
|
|
if (!card.ATSRelevant || (card.ATSRelevant && card.ATSSize == Buffer[0] - 1 && memcmp(card.ATS, Buffer + 1, card.ATSSize) == 0))
|
|
/*
|
|
* If for this candidate the ATS is not relevant, it remains being a candidate.
|
|
* If the ATS is relevant and the size is correct and the ATS is the same as the reference value, this candidate remains a candidate.
|
|
*/
|
|
continue;
|
|
|
|
// Else, we have to delete this candidate
|
|
uint8_t j;
|
|
for (j = i; j < CardCandidatesIdx - 1; j++)
|
|
CardCandidates[j] = CardCandidates[j+1];
|
|
CardCandidatesIdx--;
|
|
i--;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* If any cards are not distinguishable with ISO14443A commands only, this is the place to run some proprietary commands.
|
|
*/
|
|
|
|
if ((ReaderState >= STATE_SAK_CL1 && ReaderState <= STATE_SAK_CL3) || ReaderState == STATE_ATS)
|
|
{
|
|
uint8_t i;
|
|
for (i = 0; i < CardCandidatesIdx; i++)
|
|
{
|
|
switch (CardCandidates[i])
|
|
{
|
|
case CardType_NXP_MIFARE_DESFire:
|
|
case CardType_NXP_MIFARE_DESFire_EV1:
|
|
Buffer[0] = 0x02;
|
|
Buffer[1] = 0x60;
|
|
uint16_t crc = ISO14443_CRCA(Buffer, 2);
|
|
Buffer[2] = crc;
|
|
Buffer[3] = crc >> 8;
|
|
ReaderState = STATE_DESFIRE_INFO;
|
|
return addParityBits(Buffer, 32);
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
switch (ReaderState)
|
|
{
|
|
case STATE_DESFIRE_INFO:
|
|
if (BitCount == 0)
|
|
{
|
|
CardCandidatesIdx = 0; // this will return that this card is unknown to us
|
|
break;
|
|
}
|
|
BitCount = removeSOC(Buffer, BitCount);
|
|
if (!checkParityBits(Buffer, BitCount))
|
|
{
|
|
LogEntry(LOG_ERR_APP_CHECKSUM_FAIL, Buffer, (BitCount + 8) / 7);
|
|
CardCandidatesIdx = 0;
|
|
return Reader14443A_Deselect(Buffer);
|
|
}
|
|
BitCount = removeParityBits(Buffer, BitCount);
|
|
if (ISO14443_CRCA(Buffer, BitCount / 8))
|
|
{
|
|
CardCandidatesIdx = 0;
|
|
return Reader14443A_Deselect(Buffer);
|
|
}
|
|
switch (Buffer[3])
|
|
{
|
|
case 0x00:
|
|
CardCandidatesIdx = 1;
|
|
CardCandidates[0] = CardType_NXP_MIFARE_DESFire;
|
|
break;
|
|
|
|
case 0x01:
|
|
CardCandidatesIdx = 1;
|
|
CardCandidates[0] = CardType_NXP_MIFARE_DESFire_EV1;
|
|
break;
|
|
|
|
default:
|
|
CardCandidatesIdx = 0;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (CardCandidatesIdx == 0)
|
|
{
|
|
CommandLinePendingTaskFinished(COMMAND_INFO_OK_WITH_TEXT_ID, "Unknown card type.");
|
|
} else if (CardCandidatesIdx == 1) {
|
|
char tmpType[64];
|
|
memcpy_P(tmpType, &CardIdentificationList[CardCandidates[0]].Type, 64);
|
|
CommandLinePendingTaskFinished(COMMAND_INFO_OK_WITH_TEXT_ID, tmpType);
|
|
} else {
|
|
char tmpBuf[TERMINAL_BUFFER_SIZE];
|
|
uint16_t size = 0, tmpsize = 0;
|
|
bool enoughspace = true;
|
|
|
|
uint8_t i;
|
|
for (i = 0; i < CardCandidatesIdx; i++)
|
|
{
|
|
if (size <= TERMINAL_BUFFER_SIZE) // prevents buffer overflow
|
|
{
|
|
char tmpType[64];
|
|
memcpy_P(tmpType, &CardIdentificationList[CardCandidates[i]].Type, 64);
|
|
tmpsize = snprintf(tmpBuf + size, TERMINAL_BUFFER_SIZE - size, "%s or ", tmpType);
|
|
size += tmpsize;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
if (size > TERMINAL_BUFFER_SIZE)
|
|
{
|
|
size -= tmpsize;
|
|
enoughspace = false;
|
|
}
|
|
tmpBuf[size-4] = '.';
|
|
tmpBuf[size-3] = '\0';
|
|
CommandLinePendingTaskFinished(COMMAND_INFO_OK_WITH_TEXT_ID, tmpBuf);
|
|
if (!enoughspace)
|
|
TerminalSendString("There is at least one more card type candidate, but there was not enough terminal buffer space.\r\n");
|
|
}
|
|
// print general data
|
|
TerminalSendString("ATQA:\t");
|
|
CommandLineAppendData(&CardCharacteristics.ATQA, 2);
|
|
TerminalSendString("UID:\t");
|
|
CommandLineAppendData(CardCharacteristics.UID, CardCharacteristics.UIDSize);
|
|
TerminalSendString("SAK:\t");
|
|
CommandLineAppendData(&CardCharacteristics.SAK, 1);
|
|
|
|
Reader14443CurrentCommand = Reader14443_Do_Nothing;
|
|
CardCandidatesIdx = 0;
|
|
CodecReaderFieldStop();
|
|
Selected = false;
|
|
return 0;
|
|
}
|
|
return rVal;
|
|
}
|
|
|
|
default: // e.g. Do_Nothing
|
|
return 0;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
uint16_t ISO14443_CRCA(uint8_t * Buffer, uint8_t ByteCount)
|
|
{
|
|
uint8_t * DataPtr = Buffer;
|
|
uint16_t crc = 0x6363;
|
|
uint8_t ch;
|
|
while (ByteCount--)
|
|
{
|
|
ch = *DataPtr++ ^ crc;
|
|
ch = ch ^ (ch << 4);
|
|
crc = (crc >> 8) ^ (ch << 8) ^ (ch << 3) ^ (ch >> 4);
|
|
}
|
|
return crc;
|
|
}
|