contact smart card sticking out of a pocket

How to detect contact or contactless smartcards using PC/SC

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.

  1. Get context handle (SCardEstablishContext)
  2. Get the status of the reader that is being monitored (SCardGetStatusChange)
  3. Compare the dwCurrentState and dwEventState members in SCARD_READERSTATE to determine if there has been a status change.
  4. 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>
Comments are closed.