- Смарт-карты. Часть 4. JavaCard
- Общение с приложением
- Управление памятью
- Applet Firewall
- Атомарность и транзакции
- Основные ограничения JavaCard по сравнению с Java
- Использование Java смарт-карт для защиты ПО. Глава 1. Общие сведения
- 1. Введение
- Состав цикла:
- 2. Допущения
- 3. Базовые сведения о Java-картах
- 4. Почему Java-карты удобно использовать для защиты ПО
- 5. Можно ли взломать ПО, защищенное Java-картой?
- 6. Выбор Java карты
- 7. Где купить
- 8. Благодарность терпеливым читателям
Смарт-карты. Часть 4. JavaCard
Сегодня я бы хотел поговорить о JavaCard. Данная статья будет посвящена понятию JavaCard и обзору ее архитектуры. Если есть интерес к этой теме, то я бы мог написать отдельную серию статей, в которых будут подробно освящаться все аспекты JavaCard. Сразу скажу, что я не собираюсь учить вас, как написать свое первое приложение в JavaCard, т.к. по этому поводу уже существует слишком много статей в Интернете. Мы поговорим сегодня преимущественно о принципах работы JavaCard.
Итак, смарт-карта на основе JavaCard — это карта, на которой приложения исполняются на JavaCard Virtual Machine (ограниченная версия Java Virtual Machine, адаптированная для смарт-карт) в так называемом JavaCard Runtime Environment (который с Java Runtime Environment имеет очень мало общего).
Что касается терминологии, то приложения называются Applets и содержатся в Packages (пакетах). Пакеты распространяются в CAP-files (вместо Jar-files). Пакеты и приложения имеют собственный AID (Application Identifier). Это необходимо для того, чтобы их можно было однозначно идентифицировать в таких командах, как: SELECT, INSTALL, DELETE, и т.д. (SELECT описывается в ISO7816-4, а JavaCard и остальные команды — в Global Platform).
Жизненный цикл Applets несколько отличается от привычного жизненного цикла приложений для компьютеров. Applet — это любой класс, наследующий от базового класса «Applet». При установке приложений вызывается его статический метод install. Этот метод должен создать объект соответствующего класса и вызвать на него метод register. Впоследствии объект будет сохранен в системе и получит собственный AID, который будет использован для дальнейшего общения с приложением. Объект и его поля данных сохраняются в NVM (Non-Volatile Memory). Каждый объект или массив, созданный приложением с помощью оператора «new», также будет находиться в NVM. Это означает, что, в отличие от традиционных компьютерных программ, состояние приложений JavaCard является постоянным и не теряется даже при выключении карты.
Общение с приложением
В любой момент на каждом открытом логическом канале есть одно активное приложение. Активное приложение — это то приложение, которое получает все APDU, посылаемые терминалом. При получении APDU JavaCard вызывает метод process активного приложения, который берет полученный APDU как единственный параметр. Именно этот метод является ядром Applet, поскольку там обрабатываются запросы от терминала. Полученный объект APDU также используется для того, чтобы посылать ответы.
Applet может быть активирован двумя способами:
- при reset карты или при открытии логического канала система, как правило, активирует то приложение, которое отмечено как Default Application
- с помощью команды SELECT с P1 = 0x04 и AID (полный или частичный) приложения в Data
Когда приложение активируется с помощью команды SELECT, то сразу после активизации будет вызван метод process с APDU, содержащим данную команду. Таким образом Applet имеет возможность посылать информацию в ответ на команду SELECT при активизации. Класс Applet предоставляет метод selectingApplet() для того, чтобы определить, если полученная команда вызвала активизацию приложения.
Приложение также имеет возможность переписать методы select() и deselect(), чтобы, соотвественно, провести инициализацию при активизации и де-инициализацию при де-активизации. Эти методы будут вызваны вне зависимости от того, каким образом было активировано приложение.
Логические каналы можно открывать и закрывать с помощью команды MANAGE CHANNEL. По открытым логическим каналам можно посылать любые команды, в том числе SELECT. Именно SELECT и MANAGE CHANNEL являются единственными командами, которые обрабатываются напрямую системой, а не активным приложением. Хотя в случае команды SELECT можно сказать, что она обрабатывается и системой, и активным приложением.
Полные правила активизации приложения и обработки команды SELECT довольно сложные, и существуют маленькие противоречия между JavaCard и Global Platform. Тем не менее, я затрону данную тему в одной из моих следующих статей. Сейчас стоит сказать, что карты зачастую следуют правилам, описанным в Global Platform.
Управление памятью
Как я уже сказал выше объекты и массивы по умолчанию сохраняются в NVM. Помимо NVM, JavaCard также дает возможность создать массивы в RAM с помощью ряда методов из класса JCSystem. При этом существуют 2 вида временной памяти: Clear on Reset и Clear on Deselect. Первая стирается при выключении или reset карты. Вторая стирается тогда, когда Applet перестает быть активным (т.е: при SELECT другого приложения, выключении, reset, и т.д.). Стоит отметить, что, хотя содержание массива стирается (т.е. там пишутся все нули или false), сам массив остается, и его можно, к примеру, сохранять в поле данных объекта.
Что сохраняется в NVM:
- Все объекты и их поля данных.
- Все массивы.
- Содержание массивов, созданных оператором new.
Что сохраняется в RAM (CLEAR_ON_RESET либо CLEAR_ON_DESELECT):
- Содержание массивов, созданных функциями JCSystem.makeTransient Array. Важно отметить, что объекты, находящиеся в массиве, созданном функцией JCSystem.makeTransientObjectArray(), все же будут сохраняться в NVM. Только ссылка на них будет в RAM.
- Global Arrays: системные (APDU буфер и Install Parameters буфер), а также Global Arrays, созданные с помощью JCSystem.makeGlobalArray().
- Буфер для транзакций. Однако прямой доступ к этому буферу невозможен.
Приложение, как правило, должно создавать все нужные ему объекты и массивы при установке, а не динамически. Причиной этому являются, в основном, два фактора:
1) Желание гарантировать, что при успешной установке приложения, оно не перестанет работать, если какое-то другое приложение взяло себе всю свободную память.
2) Сборщик мусора — необязательная (хоть и очень распространенная) функция. Это означает, что при динамическом создании обьектов существует риск, что созданные объекты никогда не будут удаляться. Со временем это приведет к отсутствию свободной памяти. Даже в случае присутствия сборщика мусора, эта процедура происходит не автоматически, а по запросу приложения (JCSystem.requestObjectDeletion()).
Впоследствии код приложения редко получается «чистым» по параметрам объектно-ориентрованного программирования. Использование общего byte[] для множества различных операций — очень распространенная практика. Классы, состоящие исключительно из статических методов и работающие на byte[], тоже не редкость. К тому же, создание классов и объектов сводится к минимуму. Памяти мало и ее нужно беречь любой ценой.
Стоит также отметить роль объекта APDU. Его буфер, находящийся в RAM, стирается перед получением каждой команды. Его принято использовать не только для формирования ответа, но часто даже как временный буфер. Его размер — как минимум, 256 байтов или намного больше, если карта поддерживает Extended Length.
Applet Firewall
Важной чертой JavaCard, которая может путать программистов, является присутствие так называемого Applet Firewall. Цель Firewall — это не допускать, чтобы какой-либо Applet имел доступ к данным иного. Сразу скажу, что JavaCard допускает общение между приложениями с помощью Shareable Interfaces, а также чтобы пакеты являлись библиотеками классов и функций, которые могут быть использованы в разных приложениях. Именно поэтому возникает необходимость firewall. Основные принципы Applet Firewall следующие:
- Каждое приложение принадлежит одному контексту. Все приложения из одного пакета принадлежат одному и тому же контексту.
- Каждый объект принадлежит одному приложению или системе.
- У пакета без приложений (библиотеки) нет контекста. Объекты из его классов принадлежат приложению, которое их создало.
- В системе всегда есть один активный контекст.
- Доступ (чтение/написание в случае массива и вызов методов в случае объекта) разрешается только к объектам, принадлежащим активному контексту.
- Система имеет доступ ко всем объектам.
- Вызов статических методов всегда разрешается и исполняется в активном контексте. Доступ к статическим полям данных тоже разрешается. Однако объекты, на которые ссылаются статические поля, следуют общим правилам firewall.
- Временные массивы из типа CLEAR_ON_DESELECT доступны только тогда, когда активный контекст соответствует контексту активного приложения.
С помощью Shareable Interface программист может позволить определенным методам одного объекта быть доступными приложениям из другого контекста. Когда один из этих методов вызван, то система переключает контекст на тот контекст, которому принадлежит объект, предоставляющий Shareable Interface. Это означает, что данный метод имеет доступ только к объектам, принадлежащим его контексту, и если, к примеру, передать этому методу byte[] как параметр из оригинального контекста, то при доступе произойдет ошибка. После завершения исполнения метода контекст переключается обратно на изначальный.
Приложение также имеет доступ к определенным объектам, принадлежащим системе. Такие объекты называются Entry Points. Существуют два вида Entry Points: временные и постоянные. Постоянные Entry Points — простые, доступ к ним разрешен с любого контекста. Примером этому являются объекты из класса AID. Временные Entry Points, напротив, имеют ограничение, не позволяющее сохранять ссылки на них в полях данных объекта или статических полях класса. Пример временного Entry Point — объект APDU.
Начиная с JavaCard 3.0.4, существует также возможность создавать Global Arrays с помощью метода JCSystem.makeGlobalArray(). Их поведение точно такое же, как и у временных Entry Points. Они преимущественно нужны в качестве параметра для методов, вызванных по технике Shareable Interface.
Атомарность и транзакции
JavaCard гарантирует атомарные операции при изменении полей данных объектов или классов. Атомарность также обеспечена методами, работающими над массивами (кроме тех, которые имеют суффикс NonAtomic в названии). Это означает, что, к примеру, Util.arrayCopy либо скопирует все байты (при успешном исполнении), либо оставит массив неизменным в случае ошибки или потери энергии.
Если необходимо провести изменение в нескольких постоянных полях и массивах в одной атомарной операции, то JavaCard также предоставляет функции JCSystem.beginTransaction() для начала транзакции и JCSystem.commitTransaction() для ее завершения. Все изменения, происходящие в массивах и постоянных полях между вызовами JCSystem.beginTransaction() и JCSystem.commitTransaction(), автоматически будут являться частью транзакции. Если транзакция отменяется из-за ошибки, потери энергии или вызова метода JCSystem.abortTransaction(), то система восстановит изначальное состояние. Стоит запомнить, что техническая реализация транзакции использует дополнительный системный буфер. Если буфер заполняется полностью, то система дает ошибку TransactionException.
JavaCard поддерживает технологию RMI (Remote Metod Invocation). В этой статье я не буду освящать данную технологию. Скажу лишь только, что данная функциональность не является распространенной, и многие карты не поддерживают ее.
Большая часть JavaCard API посвящена криптографии. Есть классы для шифрования, создания и проверки цифровой подписи, генерирования ключей и случайных чисел, расчета контрольных сумм и хешов, а также для реализации схем обмена ключами. Помимо криптографии JavaCard также предоставляет классы для сохранения и проверки PIN и даже биометрических данных. Остальные классы предоставляют доступ к функциональности, описанной в других главах или являются вспомогательными для работы со строковыми, номерами, TLV и т.д. Что касается API, то он будет трактоваться в другой серии статей.
Основные ограничения JavaCard по сравнению с Java
Язык программирования приложений JavaCard — это Java. Почти Java. Так, типы char, float, double, long и enum не поддерживаются. Тип int является необязательным для карт (обычно современные карты его поддерживают) и его использование, если его не активировать соответствующей опцией, приведет к ошибке при конвертации приложения в CAP-file. Забудьте о for, итератор и lambda. Generics, статические import и аннотации (только Runtime Invisible) поддерживаются конвертером, начиная с версии 3.0.0, т.к. они не влияют на исполнение кода.
Для компилирования кода используется обыкновенный компилятор от JDK. Ошибки несовместимости будут заметны только при конвертации в CAP-file или при использовании умного IDE для JavaCard.
Самая большая проблема для программистов, на самом деле, — отсутствие int по умолчанию. Обычно они, действительно, не нужны, поэтому не хочется их активировать без надобности. Однако компилятор Java имеет привычку автоматически приводить результат всех арифметических операций к типу int. Для избежания этого в коде приходится использовать явное приведение типа к short или byte, что делает код менее читаемым.
Еще одно ограничение встречается в инициализации статических полей, а именно в том что их можно инициализировать лишь константами и массивами, содержащими константы, но не объектами.
Использование Java смарт-карт для защиты ПО. Глава 1. Общие сведения
1. Введение
В данном цикле статей пойдет речь об использовании Java смарт-карт (более дешевых аналогов электронных ключей) для защиты программного обеспечения. Цикл разбит на несколько глав.
Для прочтения и осознания информации из статей вам понадобятся следующие навыки:
- Основы разработки ПО для Windows (достаточно умения программировать в любой визуальной среде, такой как Delphi или Visual Basic)
- Базовые знания из области криптографии (что такое шифр, симметричный, ассиметричный алгоритм, вектор инициализации, CBC и т.д. Рекомендую к обязательному прочтению Прикладную Криптографию Брюса Шнайера).
- Базовые навыки программирования на любом языке, хотя бы отдаленно напоминающем Java по синтаксису (Java, C++, C#, PHP и т.д.)
Цель цикла — познакомить читателя с Ява-картами (литературы на русском языке по их использованию крайне мало). Цикл не претендует на статус «Руководства по разработке защиты ПО на основе Ява-карт» или на звание «Справочника по Ява-картам».
Состав цикла:
2. Допущения
Ни одна статья цикла не претендует на полноту и точность, хотя автор очень старался сделать всех их наиболее полными и точными.
При использовании любой информации из данного цикла статей помните, что вы делаете это на свой страх и риск. Автор не несет ответственности за любой ущерб, который вам может причинить использование этой информации.
В цикле статей идет речь о Java-картах, соответствующих стандарту GlobalPlatform версии 2.1.1 и JavaCard 2.2.1, таких, как NXP JCOP или Gemalto TOP GX. Вся информация данного цикла статей или ее часть может оказаться неприменимой к Java-картам, не соответствующим этим стандартам (исключение, конечно, составляют более поздние версии упомянутых стандартов). Использование карт, не соответствующих упомянутым стандартам для защиты ПО может оказаться небезопасным для проекта.
Поскольку одно только описание стандарта GlobalPlatform одной конкретной версии занимает около 350 печатных листов формата A4 автор сознательно идет на некоторое упрощения терминов и теорий, лежащих в основе архитектуры описываемых карт, по его мнению не противоречащее реальным данным.
3. Базовые сведения о Java-картах
Что же такое Java-карта? Java-карта — это маленький компьютер, заключенный в корпус SIM карты, подобной той, что вы используете в вашем сотовом телефоне. Этот микрокомпьютер имеет свой собственный микропроцессор (такой, как Philips P8WE5017, например), некоторый объем «flash»-памяти для хранения программ (обычно от 32 до 128 килобайт), криптографический сопроцессор.
C компьютером карта общается через специальный считыватель, обычно вставляющийся в USB порт. Но существуют и contactless карты, которые достаточно просто поднести к считывателю, чтобы установить соединение.
В памяти карты могут храниться программы, которые называются апплетами. Апплеты пишутся на слегка усеченном варианте языка Java (текстовый файл с расширением .java), затем компилируются в байт-код обычным Java-компилятором (получается *.class файл) и преобразовываются специальной программой-конвертером для непосредственной загрузки в карту (как правило, *.cap или *.ijc файл). Вариант языка Java, используемый для написания апплетов облегчен таким образом, чтобы апплеты были максимально быстрыми и компактными (например, из языка убрана поддержка таких типов, как string, а большая часть карточек не поддерживает и int. Сборщика мусора, как правило, нет — об этом в следующих статьях). Общение с картой производится путем отправки в карту команды, называемой APDU (Application protocol data unit), состоящей из заголовка, длиной в 5 байт и данных команды. В ответ карта может ответить двубайтовым кодом: либо кодом ошибки, либо кодом, означающим успешное выполнение операции (как правило, 90 00). Вместе с кодом успешного исполнения операции карта может передать приложению поток байт, соответствующий результату исполнения команды (например, расшифрованные данные).
На карте обычно по умолчанию уже существует как минимум один апплет, называемый ISD или Card Manager, который обеспечивает базовый функционал (например, установку и удаление апплетов). Иногда производитель устанавливает в карту и другие апплеты (например, апплет поддержки PKCS#11 или электронного кошелька).
Большинство Java-карт несут на себе криптографический сопроцессор, ускоряющий выполнение операций шифрования и расчетов контрольных сумм апплетами. К числу наиболее используемых относятся DES, AES, RSA и SHA1. Полный список поддерживаемых алгоритмов можно найти в JavaCard SDK. Список алгоритмов, поддерживаемых конкретной картой — в документации на конкретные карты (да, как правило, картой стандарт поддерживается не полностью, например, из-за экспортных ограничений).
Технически код апплета представляет собой экземпляр класса, унаследованного от некоторого абстрактного класса JavaCard API (Applet), методы которого обеспечивают базовый функционал (обработку установки, реакцию на команды от компьютера). Все члены класса апплета являются персистентными и хранятся в EEPROM карты. Таким образом, если у экземпляра класса апплета имеется поле short x = 5 и в результате исполнения APDU команды апплет поменял это значение, после удаления карты из компьютера и ее вставки обратно в считыватель, апплету будет доступно измененное значение.
Объем оперативной памяти карты (RAM) обычно невелик и зависит от реализации. Обычно не более 1Кб.
Для защиты карты используются ключи доступа. Обычно их три — ключ шифрования (ENC/AUTH), ключ подписи (MAC) и ключ шифрования важных данных (DEK/KEK), используется при изменении ключей доступа к карте, как правило). Обычно это 3DES2 ключи длиной 16 байт каждый. Знание этих ключей необходимо только для манипуляций с апплетом (запись и удаление, чтение списка установленных на карту апплетов). Отправка команды вашему апплету и получение ответа от него не требуют знания этих ключей.
Для общения с картой из вашего Windows-ПО используется SmartCard API, позволяющий прозрачно общаться со всеми картами, соответствующими стандарту, независимо от типа карты и считывателя. Во многих языках программирования (Java, .NET, Delphi и другие) существуют библиотеки-обертки для общения с картой, обеспечивающие требуемый функционал в удобной для разработчика форме классов / компонентов.
Некоторые карты на рынке (такие как Gemalto CyberFlex Egate 32k) имеют встроенный USB интерфейс и могут использоваться со считывателем, который можно «спаять» чуть ли не в домашних условиях. Правда, такие карты заметно дороже обычных. Стоимость считывателя для карт с USB интерфейсом при больших партиях может составлять около 1$. Стоимость стандартного считывателя, как правило, имеющего встроенные в Windows драйверы при производстве на китайских мощностях — несколько долларов.
Карты поставляются как в форм-факторе SIM-карты сотового телефона, так и в «большом» формате.
API для работы с картами умеет реагировать на вставку и извлечение карты, что позволяет реализовывать с использованием карт, например, систем учета рабочего времени сотрудников (надо уйти — поместил карту в ридер или сам ридер со своей картой в ПК. Софт отреагировал, попросил апплет подписать случайные данные, проверил подпись и произвел регистрацию).
4. Почему Java-карты удобно использовать для защиты ПО
В Java-карту владельцем может быть загружен один или несколько апплетов, реализующих практически любые алгоритмы (сложность, конечно, ограничена быстродействием процессора карты и объемом доступной памяти). Но эти апплеты не могут быть прочитаны из карты никем, включая владельца карты (апплет можно только стереть и записать новый на его место). Вы можете только передать апплету некоторую команду и получить ответ. Идеальный «черный ящик».
Стандарт GlobalPlatform допускает удаленное безопасное обновление апплетов, например, через Internet. При этом использованием криптографических алгоритмов гарантируется, что исходный код апплета не может быть перехвачен третьей стороной (мне приходилось писать клиент-серверные решения для таких операций. Не могу сказать, что все просто, поскольку карты все же разные, но, тем не менее, это вполне возможно).
Java-карта обычно защищена от электронного съема информации специальными методами (т.е. изготовить ее копию невозможно). Помните, что мы говорим о конкретных картах, реализующих конкретные стандарты. Некоторые карты Mifare, например, которые в прошлом использовал наш метрополитен, можно легко клонировать (правда, это не Java карта), тогда как произведенные по технологии IBM JCOP — обычно очень сложно.
Апплет может самостоятельно заблокировать (для разблокирования понадобится установить защищенную сессию, что требует знания ключей доступа к карте) или уничтожить карту (дальнейшая работа с ней любыми средствами будет невозможна, карта все время будет отвечать ошибкой).
Многие Java-карты сертифицированы по стандартам EAL (некоторые JCOP сертифицированы, например, по EAL4+, EAL5), что гарантирует надежную защиту. Некоторые карты используют, например, в качестве электронных кошельков такие компании как Visa.
Стоимость некоторых Java-карт при закупках в больших партиях очень мала (некоторые простые варианты JCOP стоят несколько евро при больших партиях). Сравните эту стоимость со стоимостью некоторых специализированных ключей защиты вроде HASP или Guardant, большинство вариантов которых, кстати, не позволяет располагать в памяти ключа произвольные алгоритмы.
Вариант языка Java, используемый для написания апплетов является предельно простым для изучения и использования
Все инструменты для работы с большинством карт, представленных на рынке, являются открытыми и бесплатными.
5. Можно ли взломать ПО, защищенное Java-картой?
Сложность взлома ПО, защищенного картой зависит от сложности алгоритма, который вы поместите в карту. Например, если вы расположите в карте апплет, который будет суммировать два переданных числа и возвращать результат, есть хорошие шансы, что первый же хакер изготовит эмулятор. Ему даже софт ломать для этого не придется (достаточно подменить dll в Windows, которая общается с картой). Правда, нужно заметить, что в этом случае вы сможете выпустить версию V2 вашего апплета, который будет суммировать уже три числа и будет, таким образом, “более устойчив” к взлому.
Но, например, если вы написали программное обеспечение, которое использует карту для формирования электронной подписи документов с помощью RSA ключей, которые вы поместили в апплет, такое ПО будет невозможно взломать, поскольку совершенно невозможно извлечь ключ RSA из апплета, если сам апплет этого не хочет. Впрочем, как правило, механизмы защиты, помещаемые в апплет, представляют собой нечто среднее между двумя приведенными примерами и об этом мы еще поговорим.
6. Выбор Java карты
Выбирая карты для защиты своего ПО, будьте внимательны.
- Изучите все документы и характеристики карт, которые вам предлагают.
- Убедитесь, что карты сертифицированы и защищены от атак.
- Обращайте внимание на перечень поддерживаемых картой алгоритмов. Например, на некоторых дешевых JCOP отключены AES и RSA (алгоритмы могут быть отключены не полностью. Скажем, будет доступна работа только с ключами ограниченной длины).
Что касается меня, с легким сердцем я могу советовать использовать лишь NXP JCOP или Gemalto TOP (и их старшие версии).
7. Где купить
Вообще карты лучше покупать за рубежом у проверенных поставщиков, работающих с крупными компаниями. В России цены на них просто дикие (хотя, все, конечно, зависит от количества). Но для тестов или начальной разработки их можно купить в интернет-магазинах. К сожалению, на маленькую партию карточек мало кто даст вам хорошую цену. Но на пробу вполне реально купить пару штук.
8. Благодарность терпеливым читателям
Спасибо всем, кто дочитал до этого места. Благодарности и негодования принимаются.
Буду рад любым вопросам в комментариях и постараюсь обновлять статью так, чтобы она включала ответы.