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:
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…