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 +#include +#include +#include #include #include #include @@ -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 #include +#include #include __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 #include #include #include @@ -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");