Skip to content

Commit

Permalink
Implement PC/SC retrieval of PICC UID
Browse files Browse the repository at this point in the history
The recommended way for PC/SC to determine whether the card is
contactless is to check for the artificial ATR that PC/SC creates for
contactless cards and then to attempt retrieval of the PICC UID.

The primary purpose of this change is to reject contactless cards for
now to avoid decoding their artificial ATRs. Eventually this project
will grow to support contactless cards and implement further PC/SC
contactless functionality.
  • Loading branch information
leonlynch committed Apr 7, 2024
1 parent 99abd30 commit fd86629
Show file tree
Hide file tree
Showing 3 changed files with 151 additions and 15 deletions.
115 changes: 109 additions & 6 deletions src/pcsc.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* @file pcsc.c
* @brief PC/SC abstraction
*
* Copyright (c) 2021, 2022 Leon Lynch
* Copyright (c) 2021-2022, 2024 Leon Lynch
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
Expand All @@ -29,7 +29,7 @@

#ifndef USE_PCSCLITE
// Only PCSCLite provides pcsc_stringify_error()
static char* pcsc_stringify_error(unsigned int result)
static const char* pcsc_stringify_error(unsigned int result)
{
static char str[16];
snprintf(str, sizeof(str), "0x%08X", result);
Expand All @@ -49,15 +49,27 @@ struct pcsc_t {
};

struct pcsc_reader_t {
// Populated by pcsc_init()
struct pcsc_t* pcsc;
LPCSTR name;

// Populated by SCardConnect()
SCARDHANDLE card;
DWORD protocol;

// Populated by SCardStatus()
BYTE atr[PCSC_MAX_ATR_SIZE];
DWORD atr_len;
DWORD protocol;

// Populated by pcsc_reader_connect()
uint8_t uid[10]; // See PC/SC Part 3 Rev 2.01.09, 3.2.2.1.3
size_t uid_len;
enum pcsc_card_type_t type;
};

// Helper functions
static int pcsc_reader_internal_get_uid(pcsc_reader_ctx_t reader_ctx, uint8_t* uid, size_t* uid_len);

int pcsc_init(pcsc_ctx_t* ctx)
{
struct pcsc_t* pcsc;
Expand Down Expand Up @@ -282,6 +294,59 @@ int pcsc_wait_for_card(pcsc_ctx_t ctx, unsigned long timeout_ms, size_t* idx)
return 1;
}

static int pcsc_reader_internal_get_uid(pcsc_reader_ctx_t reader_ctx, uint8_t* uid, size_t* uid_len)
{
int r;
uint8_t rx_buf[12]; // ISO 14443 Type A triple size UID + status bytes
size_t rx_len = sizeof(rx_buf);
uint8_t SW1;
uint8_t SW2;

// PC/SC GET DATA command for requesting contactless UID
// See PC/SC Part 3 Rev 2.01.09, 3.2.2.1.3
static const uint8_t pcsc_get_uid_capdu[] = { 0xFF, 0xCA, 0x00, 0x00, 0x00 };

r = pcsc_reader_trx(reader_ctx, pcsc_get_uid_capdu, sizeof(pcsc_get_uid_capdu), rx_buf, &rx_len);
if (r) {
return r;
}
if (rx_len < 2) {
// Invalid respones length
return -5;
}

// Extract status bytes
SW1 = *((uint8_t*)(rx_buf + rx_len - 2));
SW2 = *((uint8_t*)(rx_buf + rx_len - 1));

// Remove status bytes
rx_len -= 2;

// Process warning/error status bytes
// See PC/SC Part 3 Rev 2.01.09, 3.2.2.1.3, table 3-9
if (SW1 == 0x6A && SW2 == 0x81) {
// SW1-SW2 is 6A81 (Function not supported)
// Card is ISO 7816 contact
return 1;
}

if (SW1 != 0x90 || SW2 != 0x00) {
// SW1-SW2 is not 9000 (Normal)
// Unknown failure
return -6;
}


if (*uid_len < rx_len) {
// Output buffer too small
return -7;
}
memcpy(uid, rx_buf, rx_len);
*uid_len = rx_len;

return 0;
}

int pcsc_reader_connect(pcsc_reader_ctx_t reader_ctx)
{
struct pcsc_reader_t* reader;
Expand Down Expand Up @@ -316,7 +381,40 @@ int pcsc_reader_connect(pcsc_reader_ctx_t reader_ctx)
return -1;
}

return 0;
// Determine whether PC/SC ATR may be for contactless card
// See PC/SC Part 3 Rev 2.01.09, 3.1.3.2.3.1
if (reader->atr_len > 4 &&
reader->atr[0] == 0x3B &&
(reader->atr[1] & 0x80) == 0x80 &&
reader->atr[2] == 0x80 &&
reader->atr[3] == 0x01 &&
reader->atr_len == 5 + (reader->atr[1] & 0x0F)
) {
int r;

// Determine whether it is a contactless card by requesting the
// contactless UID
reader->uid_len = sizeof(reader->uid);
r = pcsc_reader_internal_get_uid(
reader_ctx,
reader->uid,
&reader->uid_len
);
if (r < 0) {
// Error during UID retrieval; assume card error
return r;
} else if (r > 0) {
// Function not supported; assume contact card
reader->type = PCSC_CARD_TYPE_CONTACT;
} else {
// UID retrieved; assume contactless card
reader->type = PCSC_CARD_TYPE_CONTACTLESS;
}
} else {
reader->type = PCSC_CARD_TYPE_CONTACT;
}

return reader->type;
}

int pcsc_reader_disconnect(pcsc_reader_ctx_t reader_ctx)
Expand All @@ -338,9 +436,10 @@ int pcsc_reader_disconnect(pcsc_reader_ctx_t reader_ctx)

// Clear card attributes
reader->card = 0;
reader->protocol = SCARD_PROTOCOL_UNDEFINED;
memset(reader->atr, 0, sizeof(reader->atr));
reader->atr_len = 0;
reader->protocol = SCARD_PROTOCOL_UNDEFINED;
reader->type = PCSC_CARD_TYPE_UNKNOWN;

return 0;
}
Expand All @@ -355,7 +454,11 @@ int pcsc_reader_get_atr(pcsc_reader_ctx_t reader_ctx, uint8_t* atr, size_t* atr_
reader = reader_ctx;

if (!reader->atr_len) {
return -1;
return 1;
}

if (reader->type != PCSC_CARD_TYPE_CONTACT) {
return 2;
}

memcpy(atr, reader->atr, reader->atr_len);
Expand Down
21 changes: 15 additions & 6 deletions src/pcsc.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* @file pcsc.h
* @brief PC/SC abstraction
*
* Copyright (c) 2021 Leon Lynch
* Copyright (c) 2021, 2024 Leon Lynch
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
Expand Down Expand Up @@ -48,6 +48,13 @@ typedef void* pcsc_reader_ctx_t; ///< PC/SC reader context pointer type

#define PCSC_MAX_ATR_SIZE (33) ///< Maximum size of ATR buffer

/// Type of card presented
enum pcsc_card_type_t {
PCSC_CARD_TYPE_UNKNOWN = 0, ///< Unknown card type
PCSC_CARD_TYPE_CONTACT, ///< ISO 7816 contact card
PCSC_CARD_TYPE_CONTACTLESS, ///< ISO 14443 contactless card
};

/**
* Initialize PC/SC context
* @param ctx PC/SC context pointer
Expand Down Expand Up @@ -103,10 +110,10 @@ int pcsc_reader_get_state(pcsc_reader_ctx_t reader_ctx, unsigned int* state);
int pcsc_wait_for_card(pcsc_ctx_t ctx, unsigned long timeout_ms, size_t* idx);

/**
* Connect to PC/SC reader.
* This function will attempt to power up the card.
* Connect to PC/SC reader, attempt to power up the card, and attempt to
* identify the type of card.
* @param reader_ctx PC/SC reader context
* @return Zero for success. Less than zero for error.
* @return Less than zero for error. Otherwise @ref pcsc_card_type_t
*/
int pcsc_reader_connect(pcsc_reader_ctx_t reader_ctx);

Expand All @@ -119,11 +126,13 @@ int pcsc_reader_connect(pcsc_reader_ctx_t reader_ctx);
int pcsc_reader_disconnect(pcsc_reader_ctx_t reader_ctx);

/**
* Retrieve ATR for current card in reader
* Retrieve ISO 7816 Answer-To-Reset (ATR) for current card in reader
* @note Although PC/SC provides an artificial ATR for contactless cards, this
* function will only retrieve the ATR for contact cards.
* @param reader_ctx PC/SC reader context
* @param atr ATR output of at most @ref PCSC_MAX_ATR_SIZE bytes
* @param atr_len Length of ATR output in bytes
* @return Zero for success. Less than zero for error.
* @return Zero for success. Less than zero for error. Greater than zero if not available.
*/
int pcsc_reader_get_atr(pcsc_reader_ctx_t reader_ctx, uint8_t* atr, size_t* atr_len);

Expand Down
30 changes: 27 additions & 3 deletions tools/emv-tool.c
Original file line number Diff line number Diff line change
Expand Up @@ -460,7 +460,7 @@ int main(int argc, char** argv)
r = pcsc_reader_get_state(reader, &reader_state);
if (r == 0) {
reader_state_str = pcsc_get_reader_state_string(reader_state);
if (reader_state) {
if (reader_state_str) {
printf("; %s", reader_state_str);
} else {
printf("; Unknown state");
Expand All @@ -483,15 +483,39 @@ int main(int argc, char** argv)
}

reader = pcsc_get_reader(pcsc, reader_idx);
printf("Card detected\n\n");
printf("%zu: %s", reader_idx, pcsc_reader_get_name(reader));
reader_state = 0;
r = pcsc_reader_get_state(reader, &reader_state);
if (r == 0) {
reader_state_str = pcsc_get_reader_state_string(reader_state);
if (reader_state_str) {
printf("; %s", reader_state_str);
} else {
printf("; Unknown state");
}
}
printf("\nCard detected\n\n");

r = pcsc_reader_connect(reader);
if (r) {
if (r < 0) {
printf("PC/SC reader activation failed\n");
goto pcsc_exit;
}
printf("Card activated\n");

switch (r) {
case PCSC_CARD_TYPE_CONTACT:
break;

case PCSC_CARD_TYPE_CONTACTLESS:
printf("Contactless not (yet) supported\n");
goto pcsc_exit;

default:
printf("Unknown card type\n");
goto pcsc_exit;
}

r = pcsc_reader_get_atr(reader, atr, &atr_len);
if (r) {
printf("Failed to retrieve ATR\n");
Expand Down

0 comments on commit fd86629

Please sign in to comment.