В результате интенсивного развития технологий, связанных с ИT-индустрией, в последнее время стали широко использоваться смарт-карты и другие подобного рода устройства. Действительно, удобно носить в кармане пиджака маленькую штучку, которая надежно защищает важную информацию, обладает значительным объемом памяти и обеспечивает самые разнообразные функции — от аутентификации в локальной сети до доступа в помещения и возможности заплатить за ужин в столовой. Да и потеря или кража устройства не откроет новому владельцу доступа к ресурсам.

Как всегда, отмечу разработчиков Microsoft. Во всех операционных системах этой компании, начиная с Windows 95/NT 4.0, реализована поддержка работы со смарт-картами. Отметим, что под словами «поддержка смарт-карт» подразумевается сложная многоуровневая система драйверов. Поддерживать необходимо как устройства для чтения карточек — ридеры, так и собственно интерфейсы обмена данными с картами. Выработано два стандарта. Для ридеров — PC/SC — стандарт, обеспечивающий PnP-подключение устройства к компьютеру. А для карт — ISO 7816 нескольких уровней, регламентирующий набор исполняемых картой команд, схемы хранения информации на карте (файловую структуру) и доступ к ней, поддерживаемые технологии защиты информации, а также формат этих команд. Большинство серийно выпускаемых устройств такие стандарты поддерживает.

Рисунок 1. Архитектура подсистемы поддержки смарт-карт в Windows.

На Рисунке 1 приведена схема архитектуры подсистемы поддержки смарт-карт Windows, которую можно найти в MSDN. Видно, что система построена в традиционном для Windows виде слоев (уровней абстракции), изолирующих соответствующие детали реализации.

Подсистема основана на стандарте PC/SC. Основными компонентами подсистемы являются:

  • Resource Manager, управляющий доступом к нескольким одновременно работающим на одном компьютере ридерам и смарт-картам. К числу его функций относятся идентификация и поддержка устройств, поддержка одновременного доступа нескольких приложений к устройствам и поддержка транзакционного механизма обмена информацией;
  • User Interface, который представляет собой стандартное диалоговое окно (common dialog box) и позволяет выбрать устройство и подсоединиться к смарт-карте;
  • несколько COM-объектов, предоставляющих доступ к специфическим функциям смарт-карт.

Для разработчиков реализовано несколько уровней API. В этой статье я расскажу о наиболее гибком из них, но и наиболее технически сложном — SC API, предоставляющем низкоуровневый интерфейс к описываемой подсистеме Windows.

В первую очередь необходимо отдавать себе отчет в том, что смарт-карта — это своего рода компьютер. Современные устройства содержат до 128 Кбайт памяти и виртуальную машину Java версии 1.2. Информация на карте хранится в виде иерархической файловой системы с весьма нетривиальными свойствами, например возможностью шифрования тех или иных файлов. Поэтому функции SCApi предназначены, в первую очередь, для установки взаимодействия с картой. Т. е. с помощью этого API можно передать команду и получить ответ. А вот как сформировать команду и интерпретировать ответ — дело разработчика. Тем более что форматы и наборы команд для разных устройств различны.

В данной статье я буду ориентироваться на смарт-карты Sample Cryptoflex 8K производства компании Schlumberger. Это простые карточки, оснащенные памятью объемом 8 Кбайт и поддерживающие основные криптографические алгоритмы. Java-машина на них не реализована. Карты недорогие, и сегодня они могут с успехом использоваться во многих приложениях. Формат команд, применяемых для этих карт, стандартный. Похожие карты выпускаются, например, компанией Siemens. Также похожий интерфейс у устройств token компании Aladdin, которые, впрочем, оснащены микросхемами Siemens (недавно компанией Aladdin совместно с НТЦ «Атлас» выпущено устройство Aladdin eToken RIC, которое содержит микросхему, удовлетворяющую всем требованиям ГОСТ 28147-89; SDK для этой микросхемы можно приобрести отдельно).

В этой статье будут описаны следующие задачи, часто возникающие при разработке приложений, ориентированных на использование смарт-карт: получение списка устройств (ридеров), мониторинг состояния ридера (карта вставлена/извлечена) и, в качестве примера работы с картой, процедура проверки пароля и получение последовательности случайных символов. Программа написана и протестирована в среде Visual C++ 6.0, но может быть перенесена без изменений в .NET. Для работы требуется Platform SDK. Приведенные примеры ориентированы на Windows 95 OSR 2.1/98/Me/NT/2000/XP.

Начало работы

Перед вызовом любых функций MS SmartCard API необходимо инициализировать библиотеку. Это делается путем вызова функции LONG ScardEstablishContext (IN DWORD dwScope, IN LPCVOID lpReserved1, IN LPCVOID lpReserved1, OUT LPSCARDCONTEXT lphContext). У этой функции только один передаваемый параметр — dwScope, который указывает уровень доступа к подсистеме. Возможны два варианта — SCARD_SCOPE_USER и SCARD_SCOPE_SYSTEM. Первый предполагает уровень доступа, соответствующий правам учетной записи пользователя, в контексте которого выполняется процесс. Второй подразумевает полный доступ. В этом случае контекст процесса должен соответствовать учетной записи, которой делегированы все права для работы с подсистемой поддержки смарт-карт. Функция возвращает параметр — значение контекста, используемого диспетчером ресурсов для доступа к внутренней базе данных Windows и содержащей информацию о картах и ридерах. Контекст используется во всех функциях, осуществляющих администрирование и взаимодействие со смарт-картами.

Получение списка подключенных ридеров

Заметим, что к одному компьютеру может быть подключено множество разнообразных устройств. Приложение должно уметь определять и идентифицировать каждое из них. Как это делается, показано в Листинге 1. Затем можно получить список подключенных ридеров. Для этого существует метод GetListReaders, инкапсулирующий функцию ScardListReaders, возвращающую SCARD_S_SUCCESS в случае успешного вызова.

Монитор текущего состояния ридеров

Ниже описана одна из самых распространенных проблем, возникающих при работе с карточками. Как приложение может узнать о том, извлечена карта или нет? Мы создадим поток, который будет оповещать приложение о произошедших событиях (в данном случае применяется функция PostMessage, но с равным успехом можно воспользоваться PostThreadMessage или функциями синхронизации).

В качестве параметра потока ему передается указатель на структуру SCREADERS_SCANER_PROC_PARAM, описываемую в Листинге 2. Функция потока приведена в Листинге 3. Возникает соблазн передать этой структуре ранее полученное значение SCARDCONTEXT, но в MSDN отмечено, что использовать в разных потоках одно значение этого параметра нельзя. Под Windows 2000 ошибка не проявляется, но под Windows 9x происходит взаимная блокировка потоков.

Поле nMessage заполняется значением, возвращаемым функцией RegisterWindowMessage ( LPCTSTR szMessageName ). Например:

lpParam->nMessage
 = RegisterWindowMessage
( «WM_SCSAMPLE_CALLBACKMESSAGE» ).

Параметры wpCardPutInValue и wpCardTakeOutValue могут принимать любые значения, удобные для использования в приложении. Полю lphExitEvent присваивается значение адреса соответствующей переменной в родительском потоке. Переменная должна быть равна NULL. Когда потребуется завершить поток, следует вызвать функцию CreateEvent, и ее результат присвоить переменной, адрес которой мы передали в функцию потока. После чего нужно дождаться его окончания с помощью вызова WaitForSingleObject (hThread,dwTime). Параметр hThread, очевидно, был возвращен приложению функцией CreateThread. Поле hWnd содержит описатель окна, которому будут передаваться сообщения о событиях со смарт-картами. Ну а о том, как создать значения полей dwReadersCount и lpReaderState, я рассказал выше.

Теперь создадим поток, функция которого приведена в Листинге 3. Напомню, что вызов функции CreateThread под Windows 9x закончится неудачей, если последний параметр — LPDWORD lpdwThreadID — будет равен NULL. Но под Windows NT/2k/XP этот указатель можно не передавать. Функция потока реализована обычным для подобных задач образом. В первую очередь следует проверить, нет ли необходимости завершить поток. Это делается с помощью функции WaitForSingleObject, которой передается параметр, задающий значение времени ожидания 0. Это означает, что, если событие не произошло, управление сразу возвращается, а если произошло, то результат функции WAIT_OBJECT_0. В последнем случае мы освобождаем память и завершаем цикл.

Далее мы проверяем состояние ридеров и, если какой-то из них изменил состояние, сообщаем об этом родительскому потоку. В данном случае используется функция PostMessage, так как сообщения отправляются окну. В противном случае, если созданный поток не имеет цикла обработки сообщений в окнах, нужно использовать функцию PostThreadMessage и вместо идентификатора окна передавать идентификатор потока, в который вы отправляете сообщение. Отмечу, что приведенная функция не отслеживает ситуацию, когда добавляется новый ридер или когда устройство чтения смарт-карт извлекают из компьютера. Реализацию этой возможности пользователь может взять на себя. Здесь я отмечу лишь, что список ридеров, который хранится в родительском потоке, должен быть идентичен списку ридеров из потока монитора.

Выбор ридера и подключение к смарт-карте

Сообщение, получаемое из потока монитора состояния карт, содержит два важных параметра — тип события и номер ридера, в который карточку вставили или, наоборот, из которого ее вытащили. Как приложение будет реагировать на извлечение карты, здесь обсуждать бессмысленно. Поговорим о том, что делать, когда карту вставляют.

Приложение получает сообщение nMessage из структуры SCREADERS_SCANER_PROC_PARAM (см. Листинг 4).

Что именно происходит в функциях SmartCardInsert и SmartCardRemove, зависит от специфики приложения. Например, при извлечении карты бухгалтерская программа может заблокировать доступ к базе данных. Или системная служба размонтирует диск или прекратит работу какого-нибудь сервера — вариантов множество.

Когда карта вставлена, логично начать с ней работать. Для этого надо инициализировать процесс взаимодействия с картой путем вызова функции LONG ScardConnect (IN SCARDCONTEXT hContext,IN LPCTSTR szReader,IN DWORD dwShareMode, IN DWORD dwPreferredProtocols, OUT LPSCARDHANDLE phCard, OUT LPDWORD pdwActiveProtocol), которой передаются текущий контекст, имя ридера, режим разделения ресурсов. Можно предоставить другим приложениям возможность работать с выбранным ридером — SCARD_SHARE_SHARED, запретить это — SCARD_SHARE_EXCLUSIVE, а также использовать режим непосредственной работы с картой — SCARD_SHARE_DIRECT, при котором другим приложениям доступ к ридеру запрещен. Взаимодействие с картой — т. е. работа с удаленным устройством — подразумевает использование того или иного протокола обмена. Определены два протокола, оба — асинхронные. Один, SCARD_PROTOCOL_T0, подразумевает обмен, ориентированный на символы, а второй, SCARD_PROTOCOL_T1, ориентирован на передачу блоков информации. Поскольку многие карты поддерживают оба протокола, параметр dwPreferredProtocols лучше инициализировать как SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1. В этом случае в параметре pdwActiveProtocol возвращается протокол, по которому будет осуществляться обмен. Ну и параметр phCard будет содержать заголовок, который далее может использоваться в других функциях.

Перед началом обмена с картой необходимо открыть транзакцию. Это не позволит другим приложениям менять информацию на карте в то время, когда вы с ней работаете. Начинает транзакцию вызов функции LONG ScardBeginTransaction (IN SCARDHANDLE hCard). Теперь мы готовы начать работать с картой.

Запись информации на карту

В данный момент нам известно, какие ридеры подключены к системе и вставлены ли в них карты. Более того, мы выбрали устройство, с которым будем работать. Теперь мы можем попробовать что-нибудь на карту записать. Весьма распространенным является восприятие смарт-карты как некоего хранилища информации. На самом деле это не так. Данные устройства, как я уже отмечал, являются вполне полноценными компьютерами. Кроме прочих функций, безусловно, они поддерживают и файловые системы, позволяющие хранить информацию. Поэтому, вообще говоря, имеет смысл говорить не о записи чего-либо на карту, а о передаче на карту и приеме от нее информации, суть команд, которые, в частности, могут что-то в файловую систему карты записывать или считывать. Но в большинстве случаев карты все же используются именно как защищенные хранилища сравнительно небольших объемов данных, и в этой статье я затрагиваю лишь эти их функции.

Обратим внимание, что задача состоит из двух частей. Первая — передача команды ридеру, и вторая — собственно формирование этой команды. Формат команды при этом зависит даже не от карт ридера, а от используемой смарт-карты.

Передача информации осуществляется с помощью приведенной функции (см. Листинг 5).

В параметре wSendStatus возвращается полученный от карты код завершения операции. Еще раз обращаю внимание читателей на то, что все операции, приводящие к тому или иному взаимодействию с картой, содержат два кода результата: первый — SmartCard API, сообщающий о результате процесса обмена, и второй — от карты, описывающий процесс обработки переданной информации непосредственно картой. Иначе говоря, вы можете успешно передать инструкции на карту, но обработать их она по тем или иным причинам, возможно, и не сумеет. В параметре wSendStatus содержится как раз эта информация.

Как правило, возвращаемый статус записывается в переменную типа WORD. Но, строго говоря, это зависит от типа используемой карты. Выбранные для экспериментов карточки Crtyptoflex 8k принадлежат именно к числу таких устройств. Однако при реализации приложения убедитесь, что применяемые карты используют такой тип возвращаемого результата.

Чтение информации с карты

Аналогично реализуется функция чтения информации, например файла с карты. Текст функции приведен в Листинге 6 и в комментариях не нуждается.

Управление смарт-картой

Теперь попробуем воспользоваться замечательными функциями, предоставляемыми смарт-картами. Еще раз обращу внимание читателей на то, что все приведенные ниже примеры ориентированы на работу с карточками Schlumberger Sample Cryptoflex 8K и с другими картами, вероятнее всего, работать не будут. Стандарт ISO 7816-4 описывает, в какой форме передаются команды на смарт-карту. А именно — формируется структура, называемая application protocol data unit (APDU), содержащая как инструкции, так и данные. APDU представляет собой структуру, приведенную в Листинге 7, за которой может следовать буфер данных, размер которого зависит от типа исполняемой команды. Для удобства работы я использовал структуру, приведенную в Листинге 8.

Как правило, работа с картой начинается с ввода пароля. Выбранные нами для примера карты поддерживают два пароля (PIN-кода): административный, который еще называют транспортным, и пользовательский. Первый предоставляет доступ ко всем функциям карты, второй — к некоторому их подмножеству; пример обработки пароля приведен в Листинге 9. Отмечу, что большая часть карт не позволяет вводить неправильные пароли произвольное число раз. Как правило, после нескольких неудачных попыток карта блокируется. Вы это наверняка знаете на примере правил использования мобильных телефонов. Ведь сердцем телефонов всех современных стандартов как раз и является SIM-карта, которая представляет собой смарт-карту. Например, во многих телефонах используются карты Schlumberger Cyberflex 32k.

В картах Cryptoflex 8K длина пароля фиксирована и равна 8 байт. Это используется в тексте функции. Возвращаемый статус проверяется на значение, соответствующее успешному завершению операции, — 0x0090.

Как я говорил выше, смарт-карта кроме непосредственного хранения информации может выполнять различные функции. Например, генерировать последовательности случайных символов — функция очень полезная при автоматической генерации паролей. Как это происходит, показано в Листинге 10. Длина последовательности задается в поле bP3 структуры SC_COMMAND_HEADER.

Описание всех команд, поддерживаемых смарт-картами Schlumberger Sample Cryptoflex 8K, можно найти в документации, входящей в состав SDK, поставляемых компанией Schlumberger-Sema. Здесь мы рассмотрели лишь примеры работы с картами, и описание полного списка поддерживаемых ими функций выходит далеко за рамки этой статьи.

Завершение работы

После того как все планируемые операции выполнены, необходимо завершить сессию. Это делается путем последовательного вызова двух функций: SCardEndTransaction и SCardDisconnect. Наборы параметров в обоих случаях одинаковые. Первый представляет собой идентификатор, полученный после вызова функции ScardConnect, а второй сообщает системе, что делать с картой после отсоединения. Возможны четыре значения этого параметра:

SCARD_LEAVE_CARD - не делать ничего;
SCARD_RESET_CARD -  заново инициализировать 
карту;
SCARD_UNPOWER_CARD - отключить питание;
SCARD_EJECT_CARD -  вынуть карту из ридера 
(для устройств чтения смарт-карт, поддерживающих 
их автоматическое извлечение).

О том, как корректно завершить поток монитора состояния ридеров, я рассказал выше.

В конце работы необходимо разрушить контекст, что делается путем вызова функции LONG ScardReleaseContext (IN SCARDCONTEXT hContext), единственным параметром которой является полученное вызовом функции SCardEstablishContext значение.

Спектр возможных применений технологий смарт-карт очень широк. Некоторые издания предрекают, что через несколько лет все мобильные компьютеры будут оснащаться встроенными ридерами смарт-карт. Такие устройства уже сейчас производит компания Fujitsu-Siemens, обеспечивая защиту от несанкционированного доступа с помощью смарт-карт на уровне BIOS, что, согласитесь, весьма удобно.

В операционных системах Windows помимо SCard API предусмотрены и другие программные интерфейсы для работы со смарт-картами. Большая их часть позволяет разработчику абстрагироваться от механизмов реализации тех или иных команд. Подробную информацию на эту тему можно найти по адресу: http://www.microsoft.com/smartcard. О стандарте PC/SC рассказано на http://www.pcscworkgroup.com.

Фото любезно предоставлены компанией Schlumberger