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 смарт-карт.
ФОН
В статье показано, как использовать Windows API PC / SC для чтения уникального идентификатора (UID) с бесконтактной карты памяти. Каждая карта содержит встроенный чип с постоянным идентификационным номером или UID. Этот номер создается в процессе производства, иногда его называют серийным номером карты. UID может быть 4 байта (32 бит), 7 байтов (56 бит) или 10 байтов (80 бит). Карта также может выдавать случайный UID.
Этот код был протестирован со следующим:
Считыватели: HID Omnikey 5021CL, ACS ACR122 и Identive CLOUD 3700 F
Карты: MIFARE Classic 1K, MIFARE Ultralight, MIFARE DESFire EV1
ЧТЕНИЕ UID
Действия, необходимые для чтения UID с бесконтактной карты, требуют следующих шагов.
1. Получите дескриптор контекста (SCardEstablishContext)
2. Подключитесь к карте на считывателе (SCardConnect)
3. Отправьте команду получения данных с помощью SCardTransmit. (См. Раздел 3.3.5.1.3 в Части 3 спецификации PC / SC для получения более подробной информации об этой команде. Ссылка в Справочниках).
Проблемой и потенциальным камнем преткновения является SCardConnect, поскольку для одного из его параметров требуется имя устройства чтения карт, а также для его работы; карта должна быть на кардридере. Можно дождаться предъявления карты, но мы обсудим это в следующей статье. Поэтому мы будем использовать функцию ListReaders из нашей предыдущей статьи (Как составить список устройств чтения смарт-карт с помощью PC / SC), чтобы получить наш список считывателей и подключиться к первому считывателю в списке. Нравится:
Новая функция-член Connect реализована следующим образом:
Если карта отсутствует, будет выдано исключение CSmartcardException с кодом ошибки SCARD_W_REMOVED_CARD (0x80100069). Если функция Init () не была вызвана до вызова этой функции, будет выдано исключение CSmartcardException с кодом ошибки SCARD_E_NO_READERS_AVAILABLE (0x8010002E).
Здесь у нас есть новый дескриптор (m_hCard, инициализированный значением NULL в конструкторе) типа HCARDHANDLE, и этот дескриптор используется с функциями, которые взаимодействуют с картой, которую мы получаем с помощью SCardConnect. SCardConnect имеет следующий синтаксис:
Большинство этих параметров довольно просты, вы заметите, что я использовал SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1 для предпочтительных протоколов, поскольку указание обоих вариантов протокола позволяет драйверу считывателя ПК / SC определять, какой из двух возможных протоколов контактных смарт-карт ISO7816-3 является подходящим. Для бесконтактных карт ни один протокол фактически не применим, поскольку команды представляют собой псевдо APDU, передаваемые считывателю (или IFD). Связь между считывателем и картой осуществляется автоматически с использованием соответствующих бесконтактных протоколов. Но чтобы интерфейс PC / SC оставался довольным, мы просто разрешим здесь оба протокола!
APDU (Application Protocol Data Unit) - это блок связи между устройством чтения смарт-карт и смарт-картой. Структура APDU определяется ISO / IEC 7816-4.
Итак, чтобы прочитать UID, нам нужно отправить APDU команды GET DATA, используя функцию SCardTransmit. APDU команды GET DATA имеет следующий формат:
Варианты P1 и P2:
ATS (ответ на выбор) может использоваться для определения производителя, типа карты и приложения.
Длина возвращаемого UID будет зависеть от используемой карты. Чтобы справиться с этим, мы можем создать функцию-член для нашего класса CSmartcard следующим образом:
Эта функция также может генерировать новый тип исключения, ее определение и реализация приведены ниже:
Чтобы использовать нашу функцию GetUID, мы можем сделать следующее:
Когда я запустил программу с моими тестовыми картами и считывающими устройствами MIFARE, я получил следующее…
UID MIFARE Ultra light и MIFARE DESFire имеют длину 56 бит, а стандартный UID MIFARE - 32 бита.
ЧТО ТЕПЕРЬ
В этом примере он не очень полезен, поскольку функция GetUID работает только в том случае, если в данный момент карта присутствует в устройстве чтения карт. Что было бы более полезным, так это иметь возможность дождаться, пока карта будет представлена в устройстве чтения карт, а затем прочитать UID или другие данные, которые будут предметом будущей статьи об использовании PC / SC.
ИСПОЛЬЗОВАННАЯ ЛИТЕРАТУРА
Технические характеристики ПК / ПК: часть 3 http://www.pcscworkgroup.com/specifications/html/pcsc3_v2.01.09/
Это вторая статья об использовании API смарт-карт PC / SC в Windows с кард-ридерами PC / SC. Код в этих статьях будет написан на C ++ 11 с использованием набора символов Unicode и протестирован в сообществе Microsoft Visual Studio 2015. Также предполагалось, что читатель знаком с разработкой на C ++ 11 и Windows. В ходе этих статей мы будем разрабатывать простой класс для работы с API смарт-карт.
ФОН
В статье показано, как использовать Windows API PC / SC для чтения уникального идентификатора (UID) с бесконтактной карты памяти. Каждая карта содержит встроенный чип с постоянным идентификационным номером или UID. Этот номер создается в процессе производства, иногда его называют серийным номером карты. UID может быть 4 байта (32 бит), 7 байтов (56 бит) или 10 байтов (80 бит). Карта также может выдавать случайный UID.
Этот код был протестирован со следующим:
Считыватели: HID Omnikey 5021CL, ACS ACR122 и Identive CLOUD 3700 F
Карты: MIFARE Classic 1K, MIFARE Ultralight, MIFARE DESFire EV1
ЧТЕНИЕ UID
Действия, необходимые для чтения UID с бесконтактной карты, требуют следующих шагов.
1. Получите дескриптор контекста (SCardEstablishContext)
2. Подключитесь к карте на считывателе (SCardConnect)
3. Отправьте команду получения данных с помощью SCardTransmit. (См. Раздел 3.3.5.1.3 в Части 3 спецификации PC / SC для получения более подробной информации об этой команде. Ссылка в Справочниках).
Проблемой и потенциальным камнем преткновения является SCardConnect, поскольку для одного из его параметров требуется имя устройства чтения карт, а также для его работы; карта должна быть на кардридере. Можно дождаться предъявления карты, но мы обсудим это в следующей статье. Поэтому мы будем использовать функцию ListReaders из нашей предыдущей статьи (Как составить список устройств чтения смарт-карт с помощью PC / SC), чтобы получить наш список считывателей и подключиться к первому считывателю в списке. Нравится:
Code:
CSmartcard smartcard;
try
{
// initialise the smart-card class
smartcard.Init();
// get a list of attached readers
auto readerList = smartcard.ListReaders();
// and connect to the first one and read the UID from the card on it.
if (readerList.size() >= 1)
{
// connect to the card reader
smartcard.Connect(readerList[0]);
}
}
catch (const CSmartcardException &ex)
{
_tprintf(_T("Error %s, 0x%08x\n"), ex.ErrorText(), ex.ErrorCode());
}
Новая функция-член Connect реализована следующим образом:
Code:
// connect to card on specified reader, throws CSmartcardException
void CSmartcard::Connect(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);
}
}
Если карта отсутствует, будет выдано исключение CSmartcardException с кодом ошибки SCARD_W_REMOVED_CARD (0x80100069). Если функция Init () не была вызвана до вызова этой функции, будет выдано исключение CSmartcardException с кодом ошибки SCARD_E_NO_READERS_AVAILABLE (0x8010002E).
Здесь у нас есть новый дескриптор (m_hCard, инициализированный значением NULL в конструкторе) типа HCARDHANDLE, и этот дескриптор используется с функциями, которые взаимодействуют с картой, которую мы получаем с помощью SCardConnect. SCardConnect имеет следующий синтаксис:
Code:
LONG WINAPI SCardConnect(
_In_ SCARDCONTEXT hContext,
_In_ LPCTSTR szReader,
_In_ DWORD dwShareMode,
_In_ DWORD dwPreferredProtocols,
_Out_ LPSCARDHANDLE phCard,
_Out_ LPDWORD pdwActiveProtocol
);
Большинство этих параметров довольно просты, вы заметите, что я использовал SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1 для предпочтительных протоколов, поскольку указание обоих вариантов протокола позволяет драйверу считывателя ПК / SC определять, какой из двух возможных протоколов контактных смарт-карт ISO7816-3 является подходящим. Для бесконтактных карт ни один протокол фактически не применим, поскольку команды представляют собой псевдо APDU, передаваемые считывателю (или IFD). Связь между считывателем и картой осуществляется автоматически с использованием соответствующих бесконтактных протоколов. Но чтобы интерфейс PC / SC оставался довольным, мы просто разрешим здесь оба протокола!
APDU (Application Protocol Data Unit) - это блок связи между устройством чтения смарт-карт и смарт-картой. Структура APDU определяется ISO / IEC 7816-4.
Итак, чтобы прочитать UID, нам нужно отправить APDU команды GET DATA, используя функцию SCardTransmit. APDU команды GET DATA имеет следующий формат:
Командование | Класс | В | P1 | P2 | Lc | Данные в | то |
Получить данные | 0xFF | 0xCA | 0x00 0x01 | 0x00 | – | – | хх |
Варианты P1 и P2:
P1 | P2 | |
0x00 | 0x00 | UID возвращается |
0x01 | 0x00 | все исторические байты из ATS карты ISO 14443 A без CRC возвращаются |
ATS (ответ на выбор) может использоваться для определения производителя, типа карты и приложения.
Длина возвращаемого UID будет зависеть от используемой карты. Чтобы справиться с этим, мы можем создать функцию-член для нашего класса CSmartcard следующим образом:
Code:
// 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, // the send buffer
_countof(sendBuffer), // the send buffer length
NULL,
receiveBuffer, // the receive buffer
&receiveLength); // the receive buffer 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;
}
Эта функция также может генерировать новый тип исключения, ее определение и реализация приведены ниже:
Code:
// 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;
};
Чтобы использовать нашу функцию GetUID, мы можем сделать следующее:
Code:
int _tmain(int argc, _TCHAR* argv[])
{
CSmartcard smartcard;
try
{
// initialise the smart-card class
//smartcard.Init();
// get a list of attached readers
auto readerList = smartcard.ListReaders();
// and connect to the first one and read the UID from the card on it.
if (readerList.size() >= 1)
{
// connect to the card reader
smartcard.Connect(readerList[0]);
uint64_t uid = smartcard.GetUID();
_tprintf(_T("UID: %I64X\n"), uid);
}
}
catch (const CSmartcardException &ex)
{
_tprintf(_T("Error %s, 0x%08x\n"), ex.ErrorText(), ex.ErrorCode());
}
return 0;
}
Когда я запустил программу с моими тестовыми картами и считывающими устройствами MIFARE, я получил следующее…
Карта | Ридеры | ||
HID Omnikey 5021CL | САУ ACR122 | Идентификационное ОБЛАКО 3700 F | |
MIFARE Standard 1K | UID: FE8C8C97 | UID: FE8C8C97 | UID: FE8C8C97 |
MIFARE Ультра легкий | UID: 4891692BF2380 | UID: 4891692BF2380 | UID: 4891692BF2380 |
MIFARE DESFire EV1 | UID: 437146AB73780 | UID: 437146AB73780 | UID: 437146AB73780 |
UID MIFARE Ultra light и MIFARE DESFire имеют длину 56 бит, а стандартный UID MIFARE - 32 бита.
ЧТО ТЕПЕРЬ
В этом примере он не очень полезен, поскольку функция GetUID работает только в том случае, если в данный момент карта присутствует в устройстве чтения карт. Что было бы более полезным, так это иметь возможность дождаться, пока карта будет представлена в устройстве чтения карт, а затем прочитать UID или другие данные, которые будут предметом будущей статьи об использовании PC / SC.
ИСПОЛЬЗОВАННАЯ ЛИТЕРАТУРА
Технические характеристики ПК / ПК: часть 3 http://www.pcscworkgroup.com/specifications/html/pcsc3_v2.01.09/