a range of PS/SC readers

How to list smart card readers using PC/SC

Introduction

This is the first of several articles on using the PC/SC smart card API in Windows with PC/SC card readers. Future articles may cover topics such as reading and writing data to MIFARE Ultralight cards, NFC Tags, using OpenPCSC. Unfortunately we cannot publish any articles using MIFARE DESFire as this technology is under strict NDA with NXP.

The code in these articles will be in C++11 using the Unicode character set and has been tested with Microsoft Visual Studio 2015 Community. It also assumed that the reader has a grasp of C++11 and Windows development. During the course of these articles we shall be developing a simple class for handling the smart card API.

Background

The article shows how to use the PC/SC Windows API to list all the attached PC/SC card readers.

Listing PC/SC readers

Listing the PC/SC reader(s) that are attached is quite simple but not without its foibles.

To use the PC/SC Smart Card API, the winscard.h header file needs to be included and linked with winscard.lib which in Microsoft Visual Studio can be done as follows:


#include <winscard.h>
#pragma comment(lib, "winscard.lib")

Before any of the Smart Card functions are used, a handle to a smart card context (SCARDCONEXT) is required, which needs to be freed when we are finished with it. Our class handles this as follows:


using CReaderList = std::vector<CString>;
 
// defines wrapper class for PC/SC smart card API
class CSmartcard
{
public:
	CSmartcard();
	~CSmartcard();
 
	// initialise interface, throws CSmartcardException
	void Init();
 
	// get a list of readers throws CSmartcardException
	const CReaderList& ListReaders();
 
protected:
	SCARDCONTEXT m_hSC;
	CReaderList m_readers;
};

// implements the smartcard class
// the constructor
CSmartcard::CSmartcard() : m_hSC(NULL) {}
 
// and the destructor
CSmartcard::~CSmartcard()
{
	if (m_hSC) 
	{
		SCardReleaseContext(m_hSC);
	}
}
 
// initialise interface
void CSmartcard::Init()
{
	if (m_hSC == NULL)
	{
		LONG ret = SCardEstablishContext(
			SCARD_SCOPE_USER,
			NULL, 
			NULL, 
			&m_hSC);
 
		if (ret != SCARD_S_SUCCESS)
		{
			throw CSmartcardException(ret);
		}
	}
}

The parameters used here for SCardEstablishContext are all that is normally required. You will notice that we are also throwing a CSmartcardException if SCardEstablishContext fails, its implementation is as follows:


// the definition of the exception class
class CSmartcardException
{
public:
	CSmartcardException(LONG errorCode) : m_errorCode(errorCode) {}

	// get error code
	inline LONG ErrorCode() const 
	{
		return m_errorCode;
	}
 
	// get text for error code
	inline CString ErrorText() const
	{
		return CString(_com_error(m_errorCode).ErrorMessage());
	}
 
protected:
	LONG m_errorCode;
};

The function for getting a list of the readers is SCardListReaders. The syntax is as follows:

LONG WINAPI SCardListReaders(

_In_     SCARDCONTEXT hContext,
_In_opt_ LPCTSTR      mszGroups,
_Out_    LPTSTR       mszReaders,
_Inout_  LPDWORD      pcchReaders

);

mszGroups Specifies which groups of readers are required and the group types here are: all readers (SCARD_ALL_READERS or NULL), the default readers (SCARD_DEFAULT_READERS), local readers (SCARD_LOCAL_READER) and system readers (SCARD_SYSTEM_READERS). Most of the time it will be sufficient to set this to NULL.
mszReaders This is a multi-string type (a null terminated buffer of concatenated null-terminated strings). Successfully getting a buffer with a complete list of readers can be done in 1 of 2 ways. Either set this parameter to NULL and pcchReaders will contain the size of the buffer required and then recall the function with a pointer to the new buffer or pass in a pointer to a pointer and set pcchReaders to SCARD_AUTOALLOCATE. Note that when using the latter option the allocated memory needs to be freed with SCardFreeMemory when it is no longer required.
pcchReaders This will contain the number of characters in mszReaders. It will need to be set to the size of the buffer (0 if mszReaders is NULL) or SCARD_AUTOALLOCATE. The length includes all null characters.

The return value will be SCARD_S_SUCCESS if the function succeeded. The following code the addition of a function that can be used to get a list of card readers.


// get a list of readers
const CReaderList& CSmartcard::ListReaders()
{
	m_readers.empty();
 
	// initialise if not already done
	Init();
 
	// will auto allocate memory for the list of readers
	TCHAR *pszReaderList = nullptr;
	DWORD len = SCARD_AUTOALLOCATE;
 
	LONG ret = SCardListReaders(
		m_hSC,
		NULL,	// groups, using null will list all readers in the system
		(LPWSTR)&pszReaderList,	// pointer where to store the readers
		&len);	// will return the length of characters
			// in the reader list buffer
 
	if (ret == SCARD_S_SUCCESS)
	{
		TCHAR *pszReader = pszReaderList;
		while (*pszReader)
		{
			m_readers.push_back(pszReader);
			pszReader += _tcslen(pszReader) + 1;
		}
 
		// free the memory
		ret = SCardFreeMemory(m_hSC, pszReaderList);
	}
	else
	{
		throw CSmartcardException(ret);
	}
 
	return m_readers;
}

To use our CSmartCard and ListReaders function we can do the following:


int _tmain(int argc, _TCHAR* argv[])
{
	// to list the card readers attached
	CSmartcard smartcard;
 
	try
	{
		// initialise the smart-card class
		smartcard.Init();
 
		// get a list of attached readers
		auto readerList = smartcard.ListReaders();
 
		for (const auto &reader : readerList)
		{
			_tprintf(_T("Reader: %s\n"), reader);
		}
	}
	catch (const CSmartcardException &ex)
	{
		_tprintf(_T("Error %s (%08x)\n"),
			ex.ErrorText(), 
			ex.ErrorCode());
	}
 
	return 0;
}

An example output is as follows:

screen showing listed smart card readers in PC/SC Windows API

 

In this case I had 3 card readers attached, an ACS ACR122, an Identiv Cloud 3700 and a Omnikey 5321, but you will notice that 4 card readers are listed! This is because the Omnikey 5321 has both a contact and a contactless interface and are treated as independent card readers.

Now what!

Once you know the name(s) of your card reader you can connect to a card that is on it and do things like: Read the UID, get the type of the card, read the data stored in the card and so on…

Comments are closed.