Использование Java смарт-карт для защиты ПО. Глава 2. Java смарт-карта с точки зрения разработчика апплетов +4


image

В данном цикле статей пойдет речь об использовании Java смарт-карт (более дешевых аналогов электронных ключей) для защиты программного обеспечения. Цикл разбит на несколько глав.

Для прочтения и осознания информации из статей вам понадобятся следующие навыки:

  • Основы разработки ПО для Windows (достаточно умения программировать в любой визуальной среде, такой как Delphi или Visual Basic)
  • Базовые знания из области криптографии (что такое шифр, симметричный, ассиметричный алгоритм, вектор инициализации, CBC и т.д. Рекомендую к обязательному прочтению Прикладную Криптографию Брюса Шнайера).
  • Базовые навыки программирования на любом языке, хотя бы отдаленно напоминающем Java по синтаксису (Java, C++, C#, PHP и т.д.)

Цель цикла — познакомить читателя с Ява-картами (литературы на русском языке по их использованию крайне мало). Цикл не претендует на статус «Руководства по разработке защиты ПО на основе Ява-карт» или на звание «Справочника по Ява-картам».

Состав цикла:




1. APDU команда.


Команда, передаваемая апплету, называется APDU командой. APDU команда состоит из пяти байт заголовка + данные. Впрочем, сами данные можно и не отправлять. Пять байт заголовка:
  • CLA — command class. Определяет класс отправляемой команды. Обычно принимает значения 0x00, 0x80, 0x84.
  • INS — command instruction. Определяет непосредственно действие, которое должен выполнить апплет.
  • P1 — первый параметр. Может передавать дополнительные инструкции апплету.
  • P2 — второй параметр. Может передавать дополнительные инструкции апплету.
  • LC — длина следующих за заголовком данных. 0, если данных нет.
  • Данные. Дополнительные данные команды.

Например:
00A4040008A000000003000000

[0x00] (CLA) [0xA4] (INS) [0x04] (P1) [0x00] (P2) [0x08] (LC) [0xA0 0x00 0x00 0x00 0x03 0x00 0x00 0x00] (DATA)


0x00, 0xA4 — это команда SELECT APPLICATION, которая выбирает апплет для последующего обмена данными. Чтобы передать команду вашему апплету, его необходимо сначала выбрать при помощи этой команды. Параметр P1 = 0x04 означает, что выбор апплета производится по его имени (AID, см. ниже). Параметр P2 = 0x00 означает, что мы хотим выбрать апплет, строго соответствующий переданному имени. В качестве данных (8 байт) мы передали имя апплета («a000000003000000» — это стандартное имя для апплета CardManager в некоторых вариантах карт). Подробнее о стандартных командах апплетов см. Global Platform Card Spec 2.1.1, стр.105

Как можно заметить максимальный размер APDU команды — 255 + 5 = 260 байт (существует, однако, и поддержка «длинных команд, но я ими никогда не пользовался). Однако на практике редко используют команды длиной более сотни байт. Я стараюсь, чтобы команды не превышали по длине 127 байт. Причина такой предосторожности простая: вам могут попасться карты, размер APDU буфера которых менее, чем 260 байт и длинную команду карта просто не примет. Придется отправлять ее частями.

Для своих апплетов лучше выбирать значение CLA в диапазоне 0xD0-0xFE, а значение INS — любое, не использующееся стандартными командами, кроме диапазонов 0x60-0x6F, 0x90-0x9F (это техническое ограничение протокола T=0).

2. AID. Пакеты


Имя апплета называется AID (Application Identifier). Несколько апплетов или просто классов могут быть объединены в пакет (фактически, понятие пакета применительно к Ява-картам такое же, как и в самом Java). У пакета тоже есть идентификатор (Package AID). Первые байты AID апплета должны быть равны AID его пакета. Например, у меня есть пакет A0101010101010, а в нем апплет с AID A010101010101055. Минимальная длина AID — 5 байт. Максимальная — 16.

3. Applet firewall. Общение апплетов.


По умолчанию апплеты полностью независимы друг от друга. У них нет никаких способов передачи данных друг другу. Однако один апплет может реализовывать интерфейс Shareable, предоставляя другим доступ к некоторым своим методам. Пересекать границу между апплетами могут только примитивные типы данных и ссылки на APDU буфер (место, где в карте обычно размещается поступившая команда и формируется ответ на нее), поскольку он, фактически, является общим для всех апплетов карты.

С помощью JavaCard API разные апплеты могут между собой „переговариваться“ (с использоваем интерфейса Shareable). Это позволяет, например, реализовать перманентный апплет, возвращающий серийный номер карты и второй апплет, реализующий набор требуемых вам функций. При этом второй апплет можно будет безопасно обновлять, а первый — оставить в карте навечно. Тогда он сможет выполнять некоторые операции, зависимые от серийного номера (например, шифровать данные уникальным для данного серийного номера образом).

4. Коды возврата.


После отработки команды апплет возвращает код результата из двух байт. Успешному коду результата может сопутствовать набор байт ответа. Список возможных кодов возврата есть здесь. Обычно, успешное завершение операции имеет код 90 00.

5. Работа с памятью.


Все члены класса апплета располагаются в EEPROM памяти карты, которая довольно медленна. Но обычно карты имеют некоторый небольшой (до нескольких килобайт) объем транзиентной памяти, которая чем-то похожа на RAM. Доступ к ней осуществляется в несколько раз быстрее. Выделяется такая память по запросу с помощью вызова JCSystem.makeTransientByteArray(). Обратите внимание, что объем этой памяти ограничен (те несколько килобайт, о которых я говорил, выделены на все апплеты карты).

6. Протоколы передачи данных.


Для общения с картами применяются два протокола — T=0 и T=1. T=0 считается ориентированным на передачу символов, а T=1 — ориентированным на передачу блоков информации. Впрочем, для нас эти нюансы значения не имеют. Просто говоря, эти протоколы отличаются лишь тем, что в случае протокола T=1 данные передаются потоком, как мы привыкли (поток-вопрос => поток-ответ), а в случае T=0 мы имеем геморрой с необходимостью по-разному формировать команду в зависимости от того, передаем мы данные или нет, ждем ли данные или нет (подробности можно увидеть в описании SCardTransmit. Тонкости протоколов я здесь рассматривать не буду (в свое время они доставили мне немного геморроя, когда у клиента неожиданно появились карты с T=1, а мой софт работал только с T=0), замечу лишь, что предпочтительнее выбирать компоненты для работы с картами, обеспечивающие прозрачную поддержку обоих протоколов. Интересующихся отличиями я адресую к моему Дельфи-компоненту, который я использую для общения с картами. Я постарался встроить туда прозрачную поддержку обоих. Насколько у меня это получилось, покажет время.

7. ATR и другая информация о картах


ATR = Answer To Reset — это набор байт, возвращаемых картой для ее идентификации. Если так получилось, что при реализации некоторого проекта вам пришлось использовать различные типы карт, изучение ATR — неплохой способ для того, чтобы их различать. Получить ATR можно с помощью вызова SCardGetAttrib.

CPLC Data (Card Production Life Cycle Data) — это набор данных о карте, которые обычно возвращает апплет CardManager при отправке в него команды 0x80 0xCA 0x9F 0x7F. Эти данные содержат некоторую системную информацию о карте, такую как, например, серийный номер EEPROM. Хочу отметить, что на уникальность, например, серийного номера EEPROM рассчитывать трудно. Если вы предполагаете иметь на карточке серийный номер, напишите апплет, который будет реализовывать чтение/запись этого номера. Поскольку в официальной документации расшифровки полей CPLC Data я не нашел, привожу структуру этих данных на Delphi тут:

 TFSCardAPICPLCData = packed record
  public
    CPLCTag: array[0..1] of Byte; // Expected: 9F 7F
    CPLCLength: Byte; // Expected: 2A
    IntegratedCircuitROMFabricator: array[0..1] of Byte;
    IntegratedCircuitROMType: array[0..1] of Byte;
    ROMOSidentifier: array[0..1] of Byte;
    OSReleaseDate: array[0..1] of Byte;
    OSReleaseLevel: array[0..1] of Byte;
    EEPROMFabricationDate: array[0..1] of Byte;
    EEPROMSerialNumber: array[0..3] of Byte;
    EEPROMBatchIdentifier: array[0..1] of Byte;
    EEPROMModuleFabricator: array[0..1] of Byte;
    EEPROMModulePackagingDate: array[0..1] of Byte;
    EEPROMIntegratedCircuitCardManufacturer: array[0..1] of Byte;
    EEPROMEmbeddingDate: array[0..1] of Byte;
    EEPROMPrePersonalizer: array[0..1] of Byte;
    EEPROMPrePersonalizationDate: array[0..1] of Byte;
    EEPROMPrePersonalizationEquipmentIdentifier: array[0..3] of Byte;
    EEPROMPersonalizer: array[0..1] of Byte;
    EEPROMPersonalizationDate: array[0..1] of Byte;
    EEPROMPersonalizationEquipmentIdentifier: array[0..3] of Byte;
  end;

Кстати, мне попадались и карты, которые в ответ за запрос CPLC возвращали ошибку. Фактически, это означает, что блок CPLC в них не был прописан при производстве.

8. Перехват данных, отправляемых в/из карты


Поскольку любой софт общается с картой через стандартные библиотеки Windows, всегда предполагайте, что любая коммуникация „туда“ и „обратно“ может быть перехвачена. Есть даже специальные утилиты для этого, вроде APDUView. Поэтому весь обмен данными с картой нужно шифровать. Что автоматически предполагает, что ваша программа должна обязательно быть защищена протектором типа Themida.

9. Немного о производительности


Я провел некоторые тесты с картами, которые у меня были на руках. Результаты тестов очень приблизительные (никаких особых условий я не создавал, апплет использовался примитивный). 12Кб (килобайт) данных карта (Gemalto TOP IM GX) может передать примерно за 2.177 секунды. Шифрование и передача 12Кб данных 3DES2 занимает уже около 8.823 секунд, AES256 — 4.420 секунд.

Может показаться, что это негусто. Однако, в этом случае своим клиентам я всегда советую вспомнить программирование для ZX Spectrum.Так пусть же свершится чудо на считанных килобайтах.

10. Благодарность терпеливым читателям


Спасибо всем, кто дочитал до этого места. Благодарности и негодования принимаются.

Буду рад любым вопросам в комментариях и постараюсь обновлять статью так, чтобы она включала ответы.




К сожалению, не доступен сервер mySQL