Skip to content

Commit

Permalink
Implement PC/SC reader property retrieval
Browse files Browse the repository at this point in the history
The pcsc_reader_get_property() function can be used to retrieve any
specific property if supported by the PC/SC reader while this project
only provides property definitions for properties that are likely to be
of interest to this project in future.

Also stringify these properties in emv-tool.
  • Loading branch information
leonlynch committed Apr 14, 2024
1 parent 831cca9 commit 489cac2
Show file tree
Hide file tree
Showing 4 changed files with 559 additions and 49 deletions.
232 changes: 232 additions & 0 deletions src/pcsc.c
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,13 @@
#include <stdio.h>
#include <string.h>

// For ntohl and friends
#if defined(HAVE_ARPA_INET_H)
#include <arpa/inet.h>
#elif defined(HAVE_WINSOCK_H)
#include <winsock.h>
#endif

#ifndef USE_PCSCLITE
// Only PCSCLite provides pcsc_stringify_error()
static const char* pcsc_stringify_error(unsigned int result)
Expand All @@ -55,6 +62,22 @@ struct pcsc_reader_features_t {
// Populated by SCardControl(CM_IOCTL_GET_FEATURE_REQUEST)
uint8_t buf[MAX_BUFFER_SIZE];
DWORD buf_len;

// Populated by SCardControl(IFD_PIN_PROPERTIES)
PIN_PROPERTIES_STRUCTURE pin_properties;
DWORD pin_properties_len;

// Populated by SCardControl(IFD_DISPLAY_PROPERTIES)
struct {
uint16_t wLcdMaxCharacters;
uint16_t wLcdMaxLines;
} __attribute__((packed))
display_properties;
DWORD display_properties_len;

// Populated by SCardControl(GET_TLV_PROPERTIES)
uint8_t properties[MAX_BUFFER_SIZE];
DWORD properties_len;
};

struct pcsc_reader_t {
Expand All @@ -81,6 +104,7 @@ struct pcsc_reader_t {

// Helper functions
static int pcsc_reader_populate_features(struct pcsc_reader_t* reader);
static int pcsc_reader_get_feature(struct pcsc_reader_t* reader, unsigned int feature, LPDWORD control_code);
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)
Expand Down Expand Up @@ -166,6 +190,7 @@ static int pcsc_reader_populate_features(struct pcsc_reader_t* reader)
SCARDHANDLE card;
DWORD protocol;
const PCSC_TLV_STRUCTURE* pcsc_tlv;
DWORD control_code;

if (!reader) {
return -1;
Expand Down Expand Up @@ -223,6 +248,87 @@ static int pcsc_reader_populate_features(struct pcsc_reader_t* reader)
++pcsc_tlv;
}

r = pcsc_reader_get_feature(reader, FEATURE_IFD_PIN_PROPERTIES, &control_code);
if (r < 0) {
r = -6;
goto exit;
}
if (r == 0) {
// Request reader TLV properties
result = SCardControl(
card,
control_code,
NULL,
0,
&reader->features.pin_properties,
sizeof(reader->features.pin_properties),
&reader->features.pin_properties_len
);
if (result != SCARD_S_SUCCESS) {
fprintf(stderr, "SCardControl(%04X) failed; result=0x%x [%s]\n", (unsigned int)control_code, (unsigned int)result, pcsc_stringify_error(result));
r = -7;
goto exit;
}
if (reader->features.pin_properties_len != sizeof(reader->features.pin_properties)) {
fprintf(stderr, "Failed to parse PC/SC reader IFD PIN properties\n");
reader->features.pin_properties_len = 0; // Invalidate buffer length
r = -8;
goto exit;
}
}

r = pcsc_reader_get_feature(reader, FEATURE_IFD_DISPLAY_PROPERTIES, &control_code);
if (r < 0) {
r = -9;
goto exit;
}
if (r == 0) {
// Request reader TLV properties
result = SCardControl(
card,
control_code,
NULL,
0,
&reader->features.display_properties,
sizeof(reader->features.display_properties),
&reader->features.display_properties_len
);
if (result != SCARD_S_SUCCESS) {
fprintf(stderr, "SCardControl(%04X) failed; result=0x%x [%s]\n", (unsigned int)control_code, (unsigned int)result, pcsc_stringify_error(result));
r = -10;
goto exit;
}
if (reader->features.display_properties_len != sizeof(reader->features.display_properties)) {
fprintf(stderr, "Failed to parse PC/SC reader IFD display properties\n");
reader->features.display_properties_len = 0; // Invalidate buffer length
r = -11;
goto exit;
}
}

r = pcsc_reader_get_feature(reader, FEATURE_GET_TLV_PROPERTIES, &control_code);
if (r < 0) {
r = -12;
goto exit;
}
if (r == 0) {
// Request reader TLV properties
result = SCardControl(
card,
control_code,
NULL,
0,
reader->features.properties,
sizeof(reader->features.properties),
&reader->features.properties_len
);
if (result != SCARD_S_SUCCESS) {
fprintf(stderr, "SCardControl(%04X) failed; result=0x%x [%s]\n", (unsigned int)control_code, (unsigned int)result, pcsc_stringify_error(result));
r = -13;
goto exit;
}
}

// Success
r = 0;
goto exit;
Expand All @@ -238,6 +344,34 @@ static int pcsc_reader_populate_features(struct pcsc_reader_t* reader)
return r;
}

static int pcsc_reader_get_feature(
struct pcsc_reader_t* reader,
unsigned int feature,
LPDWORD control_code
)
{
const PCSC_TLV_STRUCTURE* pcsc_tlv;

if (!reader) {
return -1;
}

if (!reader->features.buf_len) {
return 1;
}

pcsc_tlv = (PCSC_TLV_STRUCTURE*)reader->features.buf;
while ((void*)pcsc_tlv - (void*)reader->features.buf < reader->features.buf_len) {
if (pcsc_tlv->tag == feature && pcsc_tlv->length == 4) {
*control_code = ntohl(pcsc_tlv->value);
return 0;
}
++pcsc_tlv;
}

return 1;
}

void pcsc_release(pcsc_ctx_t* ctx)
{
struct pcsc_t* pcsc;
Expand Down Expand Up @@ -339,6 +473,104 @@ bool pcsc_reader_has_feature(pcsc_reader_ctx_t reader_ctx, unsigned int feature)
return false;
}

int pcsc_reader_get_property(
pcsc_reader_ctx_t reader_ctx,
unsigned int property,
void* value,
size_t* value_len
)
{
struct pcsc_reader_t* reader;
const uint8_t* tlv;
size_t tlv_len;

if (!reader_ctx || !value || !value_len) {
return -1;
}
reader = reader_ctx;

// Retrieve from GET_TLV_PROPERTIES
// See PC/SC Part 10 Rev 2.02.09, 2.6.14
tlv = reader->features.properties;
tlv_len = reader->features.properties_len;
while (tlv_len >= 2) { // 1-byte tag and 1-byte length
if (tlv[0] == property) {
if (tlv[1] > tlv_len - 2) {
*value_len = 0;
return -2;
}
if (tlv[1] > *value_len) {
*value_len = 0;
return -3;
}

memcpy(value, tlv + 2, tlv[1]);
*value_len = tlv[1];
return 0;
}

tlv += (2 + tlv[1]);
tlv_len -= (2 + tlv[1]);
}

// Otherwise, retrieve from IFD_PIN_PROPERTIES
// See PC/SC Part 10 Rev 2.02.09, 2.5.5
if (reader->features.pin_properties_len) {
if (property == PCSC_PROPERTY_wLcdLayout) {
if (*value_len < sizeof(reader->features.pin_properties.wLcdLayout)) {
*value_len = 0;
return -4;
}

memcpy(
value,
&reader->features.pin_properties.wLcdLayout,
sizeof(reader->features.pin_properties.wLcdLayout)
);
*value_len = sizeof(reader->features.pin_properties.wLcdLayout);
return 0;
}
}

// Otherwise, retrieve from IFD_DISPLAY_PROPERTIES
// See PC/SC Part 10 Rev 2.02.09, 2.5.6
if (reader->features.display_properties_len) {
if (property == PCSC_PROPERTY_wLcdMaxCharacters) {
if (*value_len < sizeof(reader->features.display_properties.wLcdMaxCharacters)) {
*value_len = 0;
return -5;
}

memcpy(
value,
&reader->features.display_properties.wLcdMaxCharacters,
sizeof(reader->features.display_properties.wLcdMaxCharacters)
);
*value_len = sizeof(reader->features.display_properties.wLcdMaxCharacters);
return 0;
}

if (property == PCSC_PROPERTY_wLcdMaxLines) {
if (*value_len < sizeof(reader->features.display_properties.wLcdMaxLines)) {
*value_len = 0;
return -6;
}

memcpy(
value,
&reader->features.display_properties.wLcdMaxLines,
sizeof(reader->features.display_properties.wLcdMaxLines)
);
*value_len = sizeof(reader->features.display_properties.wLcdMaxLines);
return 0;
}
}

// Property not found
*value_len = 0;
return 1;
}

int pcsc_reader_get_state(pcsc_reader_ctx_t reader_ctx, unsigned int* state)
{
struct pcsc_reader_t* reader;
Expand Down
30 changes: 30 additions & 0 deletions src/pcsc.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,21 @@ typedef void* pcsc_reader_ctx_t; ///< PC/SC reader context pointer type
#define PCSC_FEATURE_MCT_UNIVERSAL (0x09) ///< Multifunctional Card Terminal (MCT) universal commands
/// @}

/**
* @name PC/SC reader properties
* @remark See PC/SC Part 10 Rev 2.02.09, 2.6.14
* @anchor pcsc-reader-properties
*/
/// @{
#define PCSC_PROPERTY_wLcdLayout (0x01) ///< LCD Layout (from USB CCID wLcdLayout field)
#define PCSC_PROPERTY_wLcdMaxCharacters (0x04) ///< Maximum number of characters on a single line of LCD (from USB CCID wLcdLayout field)
#define PCSC_PROPERTY_wLcdMaxLines (0x05) ///< Maximum number of lines of LCD (from USB CCID wLcdLayout field)
#define PCSC_PROPERTY_bMinPINSize (0x06) ///< Minimum PIN size accepted by the reader
#define PCSC_PROPERTY_bMaxPINSize (0x07) ///< Maximum PIN size accepted by the reader
#define PCSC_PROPERTY_wIdVendor (0x0B) ///< USB Vendor ID (from USB idVendor field)
#define PCSC_PROPERTY_wIdProduct (0x0C) ///< USB Product ID (from USB idProduct field)
/// @}

/**
* @name PC/SC reader states
* @remark These are derived from PCSCLite's SCARD_STATE_* defines
Expand Down Expand Up @@ -117,6 +132,21 @@ const char* pcsc_reader_get_name(pcsc_reader_ctx_t reader_ctx);
*/
bool pcsc_reader_has_feature(pcsc_reader_ctx_t reader_ctx, unsigned int feature);

/**
* Retrieve PC/SC reader property value
* @param reader_ctx PC/SC reader context
* @param property PC/SC property to retrieve. See @ref pcsc-reader-properties "PC/SC reader properties"
* @param value Value buffer output
* @param value_len Length of value buffer in bytes
* @return Zero for success. Less than zero for error. Greater than zero if not found.
*/
int pcsc_reader_get_property(
pcsc_reader_ctx_t reader_ctx,
unsigned int property,
void* value,
size_t* value_len
);

/**
* Retrieve PC/SC reader state
* @param reader_ctx PC/SC reader context
Expand Down
37 changes: 34 additions & 3 deletions tools/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,22 @@ else()
set(PCSC_LIBRARIES PCSCLite::PCSCLite)
endif()

# Check for headers that provide ntohl/htonl and friends
include(CheckIncludeFile)
CHECK_INCLUDE_FILE(
arpa/inet.h
HAVE_ARPA_INET_H
)
CHECK_INCLUDE_FILE(
winsock.h
HAVE_WINSOCK_H
)
if(NOT HAVE_ARPA_INET_H AND NOT HAVE_WINSOCK_H)
if(BUILD_EMV_TOOL)
message(FATAL_ERROR "Failed to find either arpa/inet.h or winsock.h. This is required to build emv-tool.")
endif()
endif()

include(GNUInstallDirs) # Provides CMAKE_INSTALL_* variables and good defaults for install()

# Print helpers object library
Expand Down Expand Up @@ -74,9 +90,21 @@ endif()
# EMV processing command line tool
if(BUILD_EMV_TOOL)
if(PCSCLite_FOUND)
set_source_files_properties(../src/pcsc.c
PROPERTIES
COMPILE_DEFINITIONS USE_PCSCLITE
set_property(
SOURCE ../src/pcsc.c
APPEND PROPERTY COMPILE_DEFINITIONS USE_PCSCLITE
)
endif()
if(HAVE_ARPA_INET_H)
set_property(
SOURCE ../src/pcsc.c
APPEND PROPERTY COMPILE_DEFINITIONS HAVE_ARPA_INET_H
)
endif()
if(HAVE_WINSOCK_H)
set_property(
SOURCE ../src/pcsc.c
APPEND PROPERTY COMPILE_DEFINITIONS HAVE_WINSOCK_H
)
endif()

Expand All @@ -88,6 +116,9 @@ if(BUILD_EMV_TOOL)
if(PCSC_LIBRARIES)
target_link_libraries(emv-tool PRIVATE ${PCSC_LIBRARIES})
endif()
if(HAVE_WINSOCK_H AND NOT HAVE_ARPA_INET_H)
target_link_libraries(emv-tool PRIVATE ws2_32)
endif()

install(
TARGETS
Expand Down
Loading

0 comments on commit 489cac2

Please sign in to comment.