Lord777
Professional
- Messages
- 2,579
- Reaction score
- 1,471
- Points
- 113
ВСТУПЛЕНИЕ
Это третья статья об использовании API смарт-карт PC / SC в Windows с кардридерами PC / SC.
Код в этих статьях будет написан на C ++ 11 с использованием набора символов Unicode и протестирован в сообществе Microsoft Visual Studio 2015. Также предполагалось, что читатель знаком с разработкой на C ++ 11 и Windows. В ходе этих статей мы будем разрабатывать простой класс для работы с API смарт-карт.
К сожалению, мы не можем публиковать подробности, относящиеся к технологии MIFARE DESFire, поскольку они защищены соглашением о неразглашении NXP ™ с партнерами.
ФОН
В статье показано, как использовать Windows API PC / SC для определения момента, когда карта была подана в устройство чтения карт, а затем для считывания уникального идентификатора (UID) с карты.
Этот код был протестирован со следующими устройствами: Считыватели: HID Omnikey 5021CL, ACS ACR122 и Identive CLOUD 3700 F
Карты: MIFARE Standard 1K, MIFARE Ultralight, MIFARE DESFire EV1
ОБНАРУЖЕНИЕ КАРТ
Шаги, необходимые для обнаружения наличия бесконтактной карты, требуют следующих шагов.
Чтение изменения статуса для смарт-карт немного отличается от других операций Windows тем, что оно не управляется событиями, поэтому необходимо опросить подсистему смарт-карт. Следует отметить, что функция SCardStatusChange блокируется (т. е. она не возвращается, пока не произойдет изменение статуса или не истечет время ожидания). Чтобы предотвратить блокировку приложения, можно использовать один из следующих методов:
Чтобы прочитать изменение статуса, вызывается функция SCardGetStatusChange, которая имеет следующие параметры:
Параметр rgReaderStates представляет собой массив структур SCARD_READERSTATE. Эта структура имеет следующий синтаксис:
Переменные-члены используются следующим образом (дополнительные сведения см. в документации MSDN).
Функция SCardGetStatusChange работает, сравнивая dwCurrentState с фактическим состоянием устройства чтения. Если есть несоответствие, то dwEventState обновляется с фактическим состоянием считывателя. SCARD_STATE_CHANGED флаг также будет установлен, чтобы указать, что существует разница между 2 членами государства.
Чтобы можно было изначально получить точное состояние считывателя, для dwCurrentState должно быть установлено значение SCARD_STATE_UNAWARE, а для dwEventState - 0x00. Следующий код показывает, как этого легко добиться.
Базовый код для определения того, что читатель изменил свой статус, будет выглядеть так:
После вызова SCardGetStatusChange (), dwEventState копируется в dwCurrentState но с SCARD_STATE_CHANGED бит очищен. Затем он готов к использованию для следующего вызова SCardGetStatusChange () для обнаружения следующего изменения состояния.
Другие флаги для dwCurrentState и dwEventState:
Чтобы обнаружить наличие карты на считывателе, необходимо проверить флаг SCARD_STATUS_PRESENT. Чтобы проверить, что карта только что была представлена, этот флаг будет снят в dwCurrentState, но установлен в dwEventState. Следующий код показывает, как это достигается:
Этот метод также можно использовать для обнаружения любых других состояний состояния, а также для определения того, была ли удалена карта.
Чтобы расширить класс CSmartcard из нашей предыдущей статьи (Как читать MIFARE UID с помощью PC / SC), чтобы он мог обнаруживать карты, мы добавляем новую переменную-член в заголовок, например:
а затем инициализировать вектор в функции ListReaders:
Можно добавить новую функцию, которая будет определять, была ли карта вставлена (в случае контактной карты) или помещена в считывающее устройство (если бесконтактно). Это выглядело бы так:
Чтобы использовать эти обновления класса CSmartcard из приложения, можно использовать следующий метод.
Обратите внимание, что цикл while завершится, если на клавиатуре будет нажата любая клавиша. Если карта обнаружена, она будет прочитана, и будет прочитан UID. например
ЧТО ТЕПЕРЬ?
После того, как состояние устройства чтения карт было обнаружено, оно может быть прочитано или записано, в этой статье мы только что прочитали UID карты, но в равной степени можно было бы читать или записывать на карту, если бы это была карта памяти. Чтение данных сектора с карты MIFARE будет рассмотрено в следующей статье.
ИСХОДНЫЙ КОД
NewCard.cpp
Smartcard.h
STDAFX.H
Это третья статья об использовании API смарт-карт PC / SC в Windows с кардридерами PC / SC.
Код в этих статьях будет написан на C ++ 11 с использованием набора символов Unicode и протестирован в сообществе Microsoft Visual Studio 2015. Также предполагалось, что читатель знаком с разработкой на C ++ 11 и Windows. В ходе этих статей мы будем разрабатывать простой класс для работы с API смарт-карт.
К сожалению, мы не можем публиковать подробности, относящиеся к технологии MIFARE DESFire, поскольку они защищены соглашением о неразглашении NXP ™ с партнерами.
ФОН
В статье показано, как использовать Windows API PC / SC для определения момента, когда карта была подана в устройство чтения карт, а затем для считывания уникального идентификатора (UID) с карты.
Этот код был протестирован со следующими устройствами: Считыватели: HID Omnikey 5021CL, ACS ACR122 и Identive CLOUD 3700 F
Карты: MIFARE Standard 1K, MIFARE Ultralight, MIFARE DESFire EV1
ОБНАРУЖЕНИЕ КАРТ
Шаги, необходимые для обнаружения наличия бесконтактной карты, требуют следующих шагов.
- Получить дескриптор контекста (SCardEstablishContext)
- Получить статус читателя, за которым ведется мониторинг (SCardGetStatusChange)
- Сравните элементы dwCurrentState и dwEventState в SCARD_READERSTATE, чтобы определить, произошло ли изменение статуса.
- Выполните необходимую операцию с картой, в нашем случае считайте UID.
Чтение изменения статуса для смарт-карт немного отличается от других операций Windows тем, что оно не управляется событиями, поэтому необходимо опросить подсистему смарт-карт. Следует отметить, что функция SCardStatusChange блокируется (т. е. она не возвращается, пока не произойдет изменение статуса или не истечет время ожидания). Чтобы предотвратить блокировку приложения, можно использовать один из следующих методов:
- Используйте очень короткий тайм-аут - подходит, если в приложении есть цикл опроса.
- Запустите опрос через WM_TIMER (OnTimer в приложении MFC) снова с очень коротким таймаутом.
- Используйте отдельную нить.
Чтобы прочитать изменение статуса, вызывается функция SCardGetStatusChange, которая имеет следующие параметры:
Code:
LONG WINAPI SCardGetStatusChange(
_In_ SCARDCONTEXT hContext,
_In_ DWORD dwTimeout,
_Inout_ LPSCARD_READERSTATE rgReaderStates,
_In_ DWORD cReaders
);
Параметр rgReaderStates представляет собой массив структур SCARD_READERSTATE. Эта структура имеет следующий синтаксис:
Code:
typedef struct {
LPCTSTR szReader;
LPVOID pvUserData;
DWORD dwCurrentState;
DWORD dwEventState;
DWORD cbAtr;
BYTE rgbAtr[36];
} SCARD_READERSTATE, *PSCARD_READERSTATE, *LPSCARD_READERSTATE;
Переменные-члены используются следующим образом (дополнительные сведения см. в документации MSDN).
Переменная | Использовать |
---|---|
szReader | Имя отслеживаемого читателя, обычно это имя обычно получается через вызов функции SCardListReaders . |
pvUserData | Не используется. |
dwCurrentState | Битовое поле последнего записанного состояния считывателя устанавливается приложением. |
dwEventState | Битовое поле текущего состояния считывателя, известное подсистеме смарт-карт. |
cbAtr | Количество байтов в rgbAtr. |
rgbAtr | ATR вставленной карты. |
Чтобы можно было изначально получить точное состояние считывателя, для dwCurrentState должно быть установлено значение SCARD_STATE_UNAWARE, а для dwEventState - 0x00. Следующий код показывает, как этого легко добиться.
Code:
SCARD_READERSTATE readerState = {};
readerState.szReader = _T("Identiv CLOUD 3700 F Contactless Reader 0");
readerState.dwCurrentState = SCARD_STATE_UNAWARE;
Базовый код для определения того, что читатель изменил свой статус, будет выглядеть так:
Code:
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;
}
}
После вызова SCardGetStatusChange (), dwEventState копируется в dwCurrentState но с SCARD_STATE_CHANGED бит очищен. Затем он готов к использованию для следующего вызова SCardGetStatusChange () для обнаружения следующего изменения состояния.
Другие флаги для dwCurrentState и dwEventState:
Ценить | dwCurrentState Значение | dwEventState значение |
---|---|---|
SCARD_STATE_UNAWARE | Состояние неизвестно dwEventState будет установлено в текущее состояние считывателя при следующем вызове SCardGetStatusChange () | не используется |
SCARD_STATE_IGNORE | Не интересует этот читатель | не используется |
SCARD_STATE_CHANGED | не используется | Есть разница между ожидаемым состоянием и фактическим состоянием |
SCARD_STATE_UNKNOWN | не используется | Читатель не узнал |
SCARD_STATE_UNAVALABLE | Считыватель недоступен для использования | Состояние для этого ридера недоступно |
SCARD_STATE_EMPTY | Карты в считывателе не ожидается | Нет карты в считывателе |
SCARD_STATE_PRESENT | Ожидается, что карта в считывателе | В считывателе есть карта |
SCARD_STATE_ATRMATCH | Ожидается, что ATR карты в считывателе будет соответствовать одной из целевых карт | В считывателе есть карта с ATR, которая соответствует одной из целевых карт. |
SCARD_STATE_EXCLUSIVE | Ожидается, что карта в считывателе будет использоваться исключительно другой программой. | Карта в считывателе используется исключительно другой программой |
SCARD_STATE_INUSE | Ожидается, что карта в считывателе будет использоваться | Карта в считывателе используется |
SCARD_STATE_MUTE | Ожидается, что карта в считывателе не отвечает | В считывателе не отвечает карта |
Чтобы обнаружить наличие карты на считывателе, необходимо проверить флаг SCARD_STATUS_PRESENT. Чтобы проверить, что карта только что была представлена, этот флаг будет снят в dwCurrentState, но установлен в dwEventState. Следующий код показывает, как это достигается:
Code:
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;
}
}
Этот метод также можно использовать для обнаружения любых других состояний состояния, а также для определения того, была ли удалена карта.
Чтобы расширить класс CSmartcard из нашей предыдущей статьи (Как читать MIFARE UID с помощью PC / SC), чтобы он мог обнаруживать карты, мы добавляем новую переменную-член в заголовок, например:
Code:
protected:
std::vector<SCARD_READERSTATE> m_readerState;
а затем инициализировать вектор в функции ListReaders:
Code:
// 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;
}
Можно добавить новую функцию, которая будет определять, была ли карта вставлена (в случае контактной карты) или помещена в считывающее устройство (если бесконтактно). Это выглядело бы так:
Code:
// 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;
}
Чтобы использовать эти обновления класса CSmartcard из приложения, можно использовать следующий метод.
Code:
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());
}
Обратите внимание, что цикл while завершится, если на клавиатуре будет нажата любая клавиша. Если карта обнаружена, она будет прочитана, и будет прочитан UID. например
Code:
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
ЧТО ТЕПЕРЬ?
После того, как состояние устройства чтения карт было обнаружено, оно может быть прочитано или записано, в этой статье мы только что прочитали UID карты, но в равной степени можно было бы читать или записывать на карту, если бы это была карта памяти. Чтение данных сектора с карты MIFARE будет рассмотрено в следующей статье.
ИСХОДНЫЙ КОД
NewCard.cpp
Code:
#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
Code:
// 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
Code:
#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>