Skip to content

Commit

Permalink
Implement PC/SC reader feature list retrieval
Browse files Browse the repository at this point in the history
The pcsc_reader_has_feature() function can be used to determine whether
any specific feature is supported by the PC/SC reader while this project
only provides feature definitions for features that are likely to be of
interest to this project in future. However, none of these features are
in use by this project yet.

Also stringify these features in emv-tool.
  • Loading branch information
leonlynch committed Apr 13, 2024
1 parent 0dae7a0 commit 831cca9
Show file tree
Hide file tree
Showing 3 changed files with 187 additions and 13 deletions.
123 changes: 123 additions & 0 deletions src/pcsc.c
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@
#include "pcsc.h"

#include <winscard.h>
#include <reader.h>

#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
Expand All @@ -48,11 +51,20 @@ struct pcsc_t {
SCARD_READERSTATE* reader_states;
};

struct pcsc_reader_features_t {
// Populated by SCardControl(CM_IOCTL_GET_FEATURE_REQUEST)
uint8_t buf[MAX_BUFFER_SIZE];
DWORD buf_len;
};

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

// Populated by pcsc_reader_populate_features()
struct pcsc_reader_features_t features;

// Populated by SCardConnect()
SCARDHANDLE card;
DWORD protocol;
Expand All @@ -68,6 +80,7 @@ struct pcsc_reader_t {
};

// Helper functions
static int pcsc_reader_populate_features(struct pcsc_reader_t* reader);
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 @@ -136,10 +149,95 @@ int pcsc_init(pcsc_ctx_t* ctx)
pcsc->reader_states[i].dwCurrentState = SCARD_STATE_UNAWARE;
}

// Populate features for each reader
for (size_t i = 0; i < pcsc->reader_count; ++i) {
// Intentionally ignore errors
pcsc_reader_populate_features(&pcsc->readers[i]);
}

// Success
return 0;
}

static int pcsc_reader_populate_features(struct pcsc_reader_t* reader)
{
int r;
LONG result;
SCARDHANDLE card;
DWORD protocol;
const PCSC_TLV_STRUCTURE* pcsc_tlv;

if (!reader) {
return -1;
}

// Connect without negotiating the card protocol because the card
// may not be present yet
result = SCardConnect(
reader->pcsc->context,
reader->name,
SCARD_SHARE_DIRECT,
0,
&card,
&protocol
);
if (result != SCARD_S_SUCCESS) {
fprintf(stderr, "SCardConnect(SCARD_SHARE_DIRECT) failed; result=0x%x [%s]\n", (unsigned int)result, pcsc_stringify_error(result));
r = -2;
goto exit;
}

// Request reader features
result = SCardControl(
card,
CM_IOCTL_GET_FEATURE_REQUEST,
NULL,
0,
reader->features.buf,
sizeof(reader->features.buf),
&reader->features.buf_len
);
if (result != SCARD_S_SUCCESS) {
fprintf(stderr, "SCardControl(CM_IOCTL_GET_FEATURE_REQUEST) failed; result=0x%x [%s]\n", (unsigned int)result, pcsc_stringify_error(result));
r = -3;
goto exit;
}

// Validate feature list buffer length
if (reader->features.buf_len % sizeof(PCSC_TLV_STRUCTURE) != 0) {
fprintf(stderr, "Failed to parse PC/SC reader features\n");
reader->features.buf_len = 0; // Invalidate buffer length
r = -4;
goto exit;
}

// Validate feature list TLV lengths
// See PC/SC Part 10 Rev 2.02.09, 2.2
pcsc_tlv = (PCSC_TLV_STRUCTURE*)reader->features.buf;
while ((void*)pcsc_tlv - (void*)reader->features.buf < reader->features.buf_len) {
if (pcsc_tlv->length != 4) {
fprintf(stderr, "Failed to parse PC/SC reader features\n");
r = -5;
goto exit;
}
++pcsc_tlv;
}

// Success
r = 0;
goto exit;

exit:
// Disconnect from card reader and leave card as-is if present
result = SCardDisconnect(card, SCARD_LEAVE_CARD);
if (result != SCARD_S_SUCCESS) {
fprintf(stderr, "SCardDisconnect(SCARD_LEAVE_CARD) failed; result=0x%x [%s]\n", (unsigned int)result, pcsc_stringify_error(result));
// Intentionally ignore errors
}

return r;
}

void pcsc_release(pcsc_ctx_t* ctx)
{
struct pcsc_t* pcsc;
Expand Down Expand Up @@ -216,6 +314,31 @@ const char* pcsc_reader_get_name(pcsc_reader_ctx_t reader_ctx)
return reader->name;
}

bool pcsc_reader_has_feature(pcsc_reader_ctx_t reader_ctx, unsigned int feature)
{
struct pcsc_reader_t* reader;
const PCSC_TLV_STRUCTURE* pcsc_tlv;

if (!reader_ctx) {
return NULL;
}
reader = reader_ctx;

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

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) {
return true;
}
++pcsc_tlv;
}

return false;
}

int pcsc_reader_get_state(pcsc_reader_ctx_t reader_ctx, unsigned int* state)
{
struct pcsc_reader_t* reader;
Expand Down
21 changes: 21 additions & 0 deletions src/pcsc.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

#include <sys/cdefs.h>
#include <stddef.h>
#include <stdbool.h>
#include <stdint.h>

__BEGIN_DECLS
Expand All @@ -32,6 +33,18 @@ __BEGIN_DECLS
typedef void* pcsc_ctx_t; ///< PC/SC context pointer type
typedef void* pcsc_reader_ctx_t; ///< PC/SC reader context pointer type

/**
* @name PC/SC reader features
* @remark See PC/SC Part 10 Rev 2.02.09, 2.3
* @anchor pcsc-reader-features
*/
/// @{
#define PCSC_FEATURE_VERIFY_PIN_DIRECT (0x06) ///< Direct PIN verification
#define PCSC_FEATURE_MODIFY_PIN_DIRECT (0x07) ///< Direct PIN modification
#define PCSC_FEATURE_MCT_READER_DIRECT (0x08) ///< Multifunctional Card Terminal (MCT) direct commands
#define PCSC_FEATURE_MCT_UNIVERSAL (0x09) ///< Multifunctional Card Terminal (MCT) universal commands
/// @}

/**
* @name PC/SC reader states
* @remark These are derived from PCSCLite's SCARD_STATE_* defines
Expand Down Expand Up @@ -96,6 +109,14 @@ pcsc_reader_ctx_t pcsc_get_reader(pcsc_ctx_t ctx, size_t idx);
*/
const char* pcsc_reader_get_name(pcsc_reader_ctx_t reader_ctx);

/**
* Indicate whether PC/SC reader feature is supported
* @param reader_ctx PC/SC reader context
* @param feature PC/SC reader feature. See @ref pcsc-reader-features "PC/SC reader features"
* @return Boolean indicating whether specified PC/SC reader feature is supported.
*/
bool pcsc_reader_has_feature(pcsc_reader_ctx_t reader_ctx, unsigned int feature);

/**
* Retrieve PC/SC reader state
* @param reader_ctx PC/SC reader context
Expand Down
56 changes: 43 additions & 13 deletions tools/emv-tool.c
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
#define EMV_DEBUG_SOURCE EMV_DEBUG_SOURCE_APP
#include "emv_debug.h"

#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
Expand Down Expand Up @@ -454,19 +455,47 @@ int main(int argc, char** argv)
printf("PC/SC readers:\n");
for (size_t i = 0; i < pcsc_count; ++i) {
reader = pcsc_get_reader(pcsc, i);
printf("%zu: %s", i, pcsc_reader_get_name(reader));
printf("Reader %zu: %s\n", i, pcsc_reader_get_name(reader));

printf("\tFeatures: ");
struct {
unsigned int feature;
const char* str;
} feature_list[] = {
{ PCSC_FEATURE_VERIFY_PIN_DIRECT, "PIN verification" },
{ PCSC_FEATURE_MODIFY_PIN_DIRECT, "PIN modification" },
{ PCSC_FEATURE_MCT_READER_DIRECT, "MCT direct" },
{ PCSC_FEATURE_MCT_UNIVERSAL, "MCT universal" },
};
bool feature_found = false;
for (size_t j = 0; j < sizeof(feature_list) / sizeof(feature_list[0]); ++j) {
if (pcsc_reader_has_feature(reader, feature_list[j].feature)) {
if (feature_found) {
printf(", ");
} else {
feature_found = true;
}

printf("%s", feature_list[j].str);
}
}
if (!feature_found) {
printf("Unspecified");
}
printf("\n");

printf("\tState: ");
reader_state = 0;
reader_state_str = NULL;
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");
}
}

if (reader_state_str) {
printf("%s", reader_state_str);
} else {
printf("Unknown");
}
printf("\n");
}

Expand All @@ -483,16 +512,17 @@ int main(int argc, char** argv)
}

reader = pcsc_get_reader(pcsc, reader_idx);
printf("%zu: %s", reader_idx, pcsc_reader_get_name(reader));
printf("Reader %zu: %s", reader_idx, pcsc_reader_get_name(reader));
reader_state = 0;
reader_state_str = NULL;
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");
}
}
if (reader_state_str) {
printf("; %s", reader_state_str);
} else {
printf("; Unknown state");
}
printf("\nCard detected\n\n");

Expand Down

0 comments on commit 831cca9

Please sign in to comment.