From 831cca98df7b0b90a53196ff7fce7434d6aa3573 Mon Sep 17 00:00:00 2001
From: Leon Lynch <lynch.leon@gmail.com>
Date: Sat, 13 Apr 2024 17:10:35 +0200
Subject: [PATCH] Implement PC/SC reader feature list retrieval

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.
---
 src/pcsc.c       | 123 +++++++++++++++++++++++++++++++++++++++++++++++
 src/pcsc.h       |  21 ++++++++
 tools/emv-tool.c |  56 ++++++++++++++++-----
 3 files changed, 187 insertions(+), 13 deletions(-)

diff --git a/src/pcsc.c b/src/pcsc.c
index 7995b82..3cfedf7 100644
--- a/src/pcsc.c
+++ b/src/pcsc.c
@@ -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>
@@ -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;
@@ -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)
@@ -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;
@@ -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;
diff --git a/src/pcsc.h b/src/pcsc.h
index 15a8d00..339fb22 100644
--- a/src/pcsc.h
+++ b/src/pcsc.h
@@ -24,6 +24,7 @@
 
 #include <sys/cdefs.h>
 #include <stddef.h>
+#include <stdbool.h>
 #include <stdint.h>
 
 __BEGIN_DECLS
@@ -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
@@ -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
diff --git a/tools/emv-tool.c b/tools/emv-tool.c
index aca9cb9..c9d2bfc 100644
--- a/tools/emv-tool.c
+++ b/tools/emv-tool.c
@@ -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>
@@ -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");
 	}
 
@@ -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");