Introduction
This is the third article on using the PC/SC Smart Card API in Windows with PC/SC card readers.
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.
Unfortunately we cannot publish details specific to MIFARE DESFire technology, as these are protected under NXP™’s non-disclosure agreement with partners.
Background
The article shows how to use the PC/SC Windows API to detect when a card has been presented to the card reader and will then read the unique identifier (UID) from a the card.
This code has been tested with the following: Readers: HID Omnikey 5021CL, ACS ACR122 & Identive CLOUD 3700 F Cards: MIFARE Standard 1K, MIFARE Ultralight, MIFARE DESFire EV1
Detecting cards
The steps required in detecting the presence of a contactless card require the following steps.
- Get context handle (SCardEstablishContext)
- Get the status of the reader that is being monitored (SCardGetStatusChange)
- Compare the dwCurrentState and dwEventState members in SCARD_READERSTATE to determine if there has been a status change.
- Perform the required operation on the card, read the UID in our case.
Reading the status change for smartcards is a little different to other windows operations in that it is not event driven, so the smartcard sub-system needs to be polled. It should be noted that the SCardStatusChange function blocks (i.e. it does not return until either a status change has occurred or the timeout has expired). To prevent the application from locking up one of the following techniques could be used:
- Use a very short timeout – suitable if application has a polling loop.
- Trigger a poll via WM_TIMER (OnTimer in MFC application) again with a very short timeout.
- Use a separate thread.
To read a status change, the SCardGetStatusChange function is called which has the following parameters:
LONG WINAPI SCardGetStatusChange(
_In_ SCARDCONTEXT hContext,
_In_ DWORD dwTimeout,
_Inout_ LPSCARD_READERSTATE rgReaderStates,
_In_ DWORD cReaders
);
The rgReaderStates parameter is an array of SCARD_READERSTATE structures. This structure has the following syntax:
typedef struct {
LPCTSTR szReader;
LPVOID pvUserData;
DWORD dwCurrentState;
DWORD dwEventState;
DWORD cbAtr;
BYTE rgbAtr[36];
} SCARD_READERSTATE, *PSCARD_READERSTATE, *LPSCARD_READERSTATE;
The member variables are used as follows (see the MSDN documentation for further details).
Variable | Use |
---|---|
szReader | The name of the reader to monitor, typically this name will typically have been obtained via a SCardListReaders function call. |
pvUserData | Not used. |
dwCurrentState | A bitfield of the last recorded state of the reader, is set by the application. |
dwEventState | A bitfield of the current state of the reader as known by the smart card subsystem. |
cbAtr | The number of bytes in rgbAtr. |
rgbAtr | The ATR of the inserted card. |
The SCardGetStatusChange function works by comparing the dwCurrentState with the actual state of the reader. If there is a mismatch then the dwEventState is updated with the actual reader state. The SCARD_STATE_CHANGED flag will also be set to indicate that there is difference between the 2 state members.
So that an accurate reader state can be initially retrieved the dwCurrentState should be set to SCARD_STATE_UNAWARE and dwEventState set to 0x00. The following code shows how this can easily be achieved.
SCARD_READERSTATE readerState = {};
readerState.szReader = _T("Identiv CLOUD 3700 F Contactless Reader 0");
readerState.dwCurrentState = SCARD_STATE_UNAWARE;
The basic code to detect that a reader has changed its status would look like this:
long ret = SCardGetStatusChange(hSC, 1, &readerState, 1);
if (ret == SCARD_SUCCESS)
{
if (readerState.dwEventState & SCARD_STATE_CHANGED)
{
// the reader state has changed....
OnStateChange(readerState);
// clear the SCARD_STATE_CHANGED flag
readerState.dwCurrentState = readerState.dwEventState & ~SCARD_STATE_CHANGED;
}
}
After the call to SCardGetStatusChange(), dwEventState is copied to dwCurrentState but with the SCARD_STATE_CHANGED bit cleared. This is then ready to be used for the next call to SCardGetStatusChange() in order to detect the next state change.
The other flags for dwCurrentState and dwEventState are:
Value | dwCurrentState Meaning | dwEventState meaning |
---|---|---|
SCARD_STATE_UNAWARE | State is unknown dwEventState will be set to the current state of the reader on the next call to SCardGetStatusChange() | not used |
SCARD_STATE_IGNORE | Not interested in this reader | not used |
SCARD_STATE_CHANGED | not used | There is a difference between the expected state and the actual state |
SCARD_STATE_UNKNOWN | not used | The reader is not recognized |
SCARD_STATE_UNAVALABLE | Reader not expected to be available for use | The state for this reader is not available |
SCARD_STATE_EMPTY | No card in reader is expected | No Card in reader |
SCARD_STATE_PRESENT | Card in reader is expected | There is a card in the reader |
SCARD_STATE_ATRMATCH | ATR of card in reader expected to match one of the target cards | There is a card in the reader with an ATR that matches one of the target cards. |
SCARD_STATE_EXCLUSIVE | Card in reader is expected to be in exclusive use by another program | Card in reader is used exclusively by another program |
SCARD_STATE_INUSE | Card in the reader is expected to be in use | Card in reader is in use |
SCARD_STATE_MUTE | Unresponsive card in the reader is expected | There is an unresponsive card in the reader |
In order to detect the presence of a card on the reader, the SCARD_STATUS_PRESENT flag needs to be checked. To test that the card has just been presented then this flag will be clear in dwCurrentState but set in dwEventState. The following code illustrates how that is achieved:
bool foundCard = false;
long ret = SCardGetStatusChange(m_hSC, timeout, &readerState, 1);
if (ret == SCARD_S_SUCCESS)
{
if (readerState.dwEventState & SCARD_STATE_CHANGED)
{
if (((readerState.dwCurrentState & SCARD_STATE_PRESENT) == 0) &&
(readerState.dwEventState & SCARD_STATE_PRESENT))
{
// have a card....
}
readerState.dwCurrentState = readerState.dwEventState & ~SCARD_STATE_CHANGED;
}
}
This technique can also be used to detect any of the other state conditions as well as detecting if a card has been removed.
To extend the CSmartcard class from our previous article (How to read a MIFARE UID using PC/SC) so it can detect cards we add a new member variable to the header, like this:
protected:
std::vector<SCARD_READERSTATE> m_readerState;
and then initialise the vector in the ListReaders function:
// get a list of readers
const CReaderList& CSmartcard::ListReaders()
{
m_readers.empty();
m_readerState.clear();
// 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);
// and set up the reader state list
for (const auto &reader : m_readers)
{
SCARD_READERSTATE readerState = {};
readerState.szReader = reader;
readerState.dwCurrentState = SCARD_STATE_UNAWARE;
m_readerState.push_back(readerState);
}
}
else
{
throw CSmartcardException(ret);
}
return m_readers;
}
A new function can be added that will detect if a card has been inserted (in the case of a contact card) or placed on a reader (if contactless). It would look like this:
// waits for a card to be presented to a reader
bool CSmartcard::WaitForCard(CString & readerName, DWORD timeout)
{
bool foundCard = false;
long ret = SCardGetStatusChange(m_hSC, timeout, m_readerState.data(), m_readerState.size());
if (ret == SCARD_S_SUCCESS)
{
// which reader had a card
for (auto pos = m_readerState.begin(); (pos != m_readerState.end()) && !found; ++pos)
{
if (pos->dwEventState & SCARD_STATE_CHANGED)
{
if (((pos->dwCurrentState & SCARD_STATE_PRESENT) == 0) &&
(pos->dwEventState & SCARD_STATE_PRESENT))
{
readerName = pos->szReader;
foundCard = true;
}
pos->dwCurrentState = pos->dwEventState & ~SCARD_STATE_CHANGED;
}
}
}
return foundCard;
}
To use these updates to the CSmartcard class from an application the following method could be used.
CSmartcard smartcard;
try
{
// initialise the smart-card class
smartcard.Init();
// get a list of attached readers
smartcard.ListReaders();
bool finished = false;
while (!finished)
{
// test for key press to exit loop
if (_kbhit())
{
finished = true;
}
// and wait for 1ms for a card to be presented
CString readerName;
if (smartcard.WaitForCard(readerName, 1))
{
// connect to this reader
smartcard.Connect(readerName);
_tprintf(_T("Card on reader %s - UID: %I64X\n"),
readerName,
smartcard.GetUID());
}
}
}
catch (const CSmartcardException &ex)
{
_tprintf(_T("Error %s, 0x%08x\n"), ex.ErrorText(), ex.ErrorCode());
}
Note that the while loop will exit if any key is pressed on the keyboard. If a card is detected then it will be read and the UID is read. e.g.
Card on reader Identiv CLOUD 3700 F Contactless Reader 0 - UID: 42E18F2893180
Card on reader Identiv CLOUD 3700 F Contactless Reader 0 - UID: FE8C8C97
Card on reader Identiv CLOUD 3700 F Contactless Reader 0 - UID: 4B90DEA704880
Now what?
Once the state of a card reader has been detected, it can be read or written to, in this article we have just read the card’s UID but it would, equally, be possible to read or write to the card if it was a memory card. Reading sector data from a MIFARE card will be covered in a further article to follow.
Source code
NewCard.cpp
#include "stdafx.h"
#include "Smartcard.h"
int _tmain(int argc, _TCHAR* argv[])
{
CSmartcard smartcard;
try
{
// initialise the smart-card class
smartcard.Init();
// get a list of attached readers
smartcard.ListReaders();
bool finished = false;
while (!finished)
{
// test for key press to exit loop
if (_kbhit())
{
finished = true;
}
// and wait for 1ms for a card to be presented
CString readerName;
if (smartcard.WaitForCard(readerName, 1))
{
// connect to this reader
smartcard.Connect(readerName);
_tprintf(_T("Card on reader %s - UID: %I64X\n"),
readerName,
smartcard.GetUID());
}
}
}
catch (const CSmartcardException &ex)
{
_tprintf(_T("Error %s, 0x%08x\n"), ex.ErrorText(), ex.ErrorCode());
}
return 0;
}
Smartcard.h
// defines wrapper class for PC/SC smartcard API
#pragma once
#include <vector>
#include <comdef.h>
#include <stdint.h>
#include <winscard.h>
// also need to link in winscard.lib
#pragma comment(lib, "winscard.lib")
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();
// connect to card on specified reader, throws CSmartcardException
void Connect(const CString &reader);
// gets the UID from the current card
// returns as unsigned 64 bit int
// throws CSmardcardException or CAPDUException
uint64_t GetUID();
// wait for card
bool WaitForCard(CString &readerName, DWORD timeout);
protected:
SCARDCONTEXT m_hSC;
SCARDHANDLE m_hCard;
DWORD m_activeProtocol;
CReaderList m_readers;
std::vector<SCARD_READERSTATE> m_readerState;
};
// 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;
};
// exception class for APDU errors
class CAPDUException
{
public:
CAPDUException(const UCHAR *error)
: CAPDUException(error[0], error[1])
{
}
CAPDUException(UCHAR e1, UCHAR e2)
{
m_errorCode = (static_cast<USHORT>(e1) << 8)
| static_cast<USHORT>(e2);
}
// get the error code
inline USHORT GetErrorCode() const
{
return m_errorCode;
}
inline CString GetErrorCodeText() const
{
CString str;
str.Format(_T("%04x"), m_errorCode);
return str;
}
protected:
USHORT m_errorCode;
};
Smartcard.cpp
#include "stdafx.h"
#include "Smartcard.h"
// the constructor
CSmartcard::CSmartcard() :m_hSC(NULL), m_hCard(NULL)
{
}
// and the destructor
CSmartcard::~CSmartcard()
{
if (m_hCard)
{
SCardDisconnect(m_hSC, m_hCard);
}
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);
}
}
}
// get a list of readers
const CReaderList& CSmartcard::ListReaders()
{
m_readers.empty();
m_readerState.clear();
// 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);
// and set up the reader state list
for (const auto &reader : m_readers)
{
SCARD_READERSTATE readerState = {0};
readerState.szReader = reader;
readerState.dwCurrentState = SCARD_STATE_UNAWARE;
m_readerState.push_back(readerState);
}
}
else
{
throw CSmartcardException(ret);
}
return m_readers;
}
// connect to card on specified reader, throws CSmartcardException
void CSmartcard::Connect(const CString &reader)
{
DWORD protocols(SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1);
m_activeProtocol = 0;
LONG ret = SCardConnect(m_hSC,
reader,
SCARD_SHARE_SHARED,
protocols,
&m_hCard,
&m_activeProtocol);
if (ret != SCARD_S_SUCCESS)
{
throw CSmartcardException(ret);
}
}
// gets the UID from the current card, throws CSmardcardException
// returns as unsigned 64 bit unsigned int
uint64_t CSmartcard::GetUID()
{
uint64_t uid(0);
// check that have a card handle
if (m_hCard == NULL)
{
throw CSmartcardException(SCARD_E_INVALID_HANDLE);
}
// create the get data APDU
UCHAR sendBuffer[] =
{
0xff, // CLA - the instruction class
0xCA, // INS - the instruction code
0x00, // P1 - 1st parameter to the instruction
0x00, // P2 - 2nd parameter to the instruction
0x00 // Le - size of the transfer
};
UCHAR receiveBuffer[32];
DWORD sendLength();
DWORD receiveLength(_countof(receiveBuffer));
// set up the io request
SCARD_IO_REQUEST ioRequest;
ioRequest.dwProtocol = m_activeProtocol;
ioRequest.cbPciLength = sizeof(ioRequest);
LONG ret = SCardTransmit(m_hCard, &ioRequest,
sendBuffer, _countof(sendBuffer), // the send buffer & length
NULL,
receiveBuffer, &receiveLength); // the receive buffer and length
if (ret == SCARD_S_SUCCESS)
{
// have received a response. Check that did not get an error
if (receiveLength >= 2)
{
// do we have an error
if ((receiveBuffer[receiveLength - 2] != 0x90) ||
(receiveBuffer[receiveLength - 1] != 0x00))
{
throw CAPDUException(
&receiveBuffer[receiveLength - 2]);
}
else if (receiveLength > 2)
{
for (DWORD i = 0; i != receiveLength - 2; i++)
{
uid <<= 8;
uid |= static_cast<uint64_t>(receiveBuffer[i]);
}
}
}
else
{
// didn't get a recognisable response,
// so throw a generic read error
throw CSmartcardException(ERROR_READ_FAULT);
}
}
else
{
throw CSmartcardException(ret);
}
return uid;
}
// waits for a card to be presented to a reader
bool CSmartcard::WaitForCard(CString & readerName, DWORD timeout)
{
bool foundCard = false;
long ret = SCardGetStatusChange(m_hSC, timeout, m_readerState.data(), m_readerState.size());
if (ret == SCARD_S_SUCCESS)
{
// which reader had a card
for (auto pos = m_readerState.begin(); pos != m_readerState.end(); ++pos)
{
_tprintf(_T(" -- reader: %s - state: 0x%x\n"), pos->szReader, pos->dwEventState);
if (pos->dwEventState & SCARD_STATE_CHANGED)
{
if (((pos->dwCurrentState & SCARD_STATE_PRESENT) == 0) &&
(pos->dwEventState & SCARD_STATE_PRESENT))
{
readerName = pos->szReader;
foundCard = true;
}
pos->dwCurrentState = pos->dwEventState & ~SCARD_STATE_CHANGED;
}
}
}
return foundCard;
}
stdafx.h
#pragma once
#include "targetver.h"
#include <stdio.h>
#include <tchar.h>
#include <conio.h>
#define _ATL_CSTRING_EXPLICIT_CONSTRUCTORS // some CString constructors will be explicit
#include <atlbase.h>
#include <atlstr.h>