Anchor |
---|
| _Toc244330123 |
---|
| _Toc244330123 |
---|
|
Anchor |
---|
| _Toc326167579 |
---|
| _Toc326167579 |
---|
|
Выбор кода для размещения в ключеЭто самый ответственный и нетривиальный этап разработки загружаемого кода. Суть его заключается в том, что разработчику нужно решить, какой именно код будет выполняться в электронном ключе.
Код должен быть устроен таким образом, чтобы выполнять определенную конечную задачу, которая в общем виде выглядит так:
...
Существует целый ряд требований к этому коду, налагающих достаточно серьезные ограничения на выбор. Требования условно можно разделить на несколько типов:
Anchor |
---|
| _Toc244330124 |
---|
| _Toc244330124 |
---|
|
Требования по безопасностиЗагружаемый код должен быть достаточно сложным, чтобы брутфорс или иные (более эффективные и продвинутые) методы анализа черных ящиков не сделали возможным создания эмулятора в короткое время.
Этот код должен отсутствовать в более ранних версиях приложения. Несоблюдение этого условия делает возможным сравнение версий приложения и нахождение перенесенного кода для внедрения его в эмулятор.
Anchor |
---|
| _Toc244330125 |
---|
| _Toc244330125 |
---|
|
Требования по производительностиКонтроллер ключа обладает достаточно большими вычислительными возможностями, однако мощность его все же гораздо ниже, чем у современных процессоров. Поэтому важно, чтобы код не был слишком ресурсоемким, в противном случае время его выполнения может возрасти до неприемлемого уровня.
Кроме того, код, загружаемый в ключ, не должен вызываться слишком часто, например, в цикле. Если при единичном вызове задержка при выполнении не будет значительной, то при циклическом вызове она может оказаться очень существенной.
Anchor |
---|
| _Toc244330126 |
---|
| _Toc244330126 |
---|
|
Требования по реализуемостиКод, размещаемый в электронном ключе, не должен:
...
Коду, исполняемому внутри ключа, доступны лишь стандартные библиотеки С и функции для работы с электронным ключом.
Ключи Guardant Code позволяют исполнять алгоритмы до 20 тысяч строк кода на С (до 60 тысяч строк в моделях ключей с увеличенным размером памяти). Соответственно, размер кода должен укладываться в эти пределы.
Anchor |
---|
| _Toc244330127 |
---|
| _Toc244330127 |
---|
|
Anchor |
---|
| _Toc326167580 |
---|
| _Toc326167580 |
---|
|
Средства разработкиНесмотря на то, что для процессоров CORTEX-M3 существуют компиляторы многих языков программирования, эффективнее всего использовать язык C, а точнее его подмножество, основой для которого является стандарт ANSI C.
Требование соблюдения стандарта связано с тем, что первоначальная разработка и отладка загружаемого кода может выполняться с использованием различных компиляторов. Т. е. предполагается, что код разрабатывается в привычной интегрированной среде, а затем компилируется для использования в Guardant Code. Поэтому для разработки загружаемого кода пригодятся навыки кроссплатформенного программирования.
Код перед загрузкой в ключ должен быть скомпилирован. Поскольку система команд процессора архитектуры CORTEX-M3, используемого в ключе, отличается от системы команд x86-совместимых процессоров, для работы потребуется компилятор, способный генерировать двоичный код, совместимый с архитектурой CORTEX-M3.
Для компиляции загружаемого кода можно применять как коммерческие средства, так и распространяемые под различными «свободными» лицензиями. К последним можно отнести GCC (включая различные решения на его основе, такие как YAGARTO).
Большинство средств разработки, распространяемых под свободными лицензиями, рассчитаны на работу под Linux. Тем не менее, существуют решения и для Windows. Кроме того, есть возможность использовать средства разработки, предназначенные для Linux под операционными системами семейства Windows, применяя для их запуска Cygwin или MinGW.
Примеры и makefile, входящие в комплект разработчика Guardant, рассчитаны на использование компилятора GCC и инструментария YAGARTO, как на самый доступный вариант.
Хотя для большинства высокоуровневых языков программирования перенос кода на С достаточно несложен, но альтернативно, можно использовать и другие языки программирования высокого уровня, для которых есть компилятор CORTEX-M3. Однако в данном документе эта возможность не описывается.
Anchor |
---|
| _Toc244330128 |
---|
| _Toc244330128 |
---|
|
Anchor |
---|
| _Guardant_Code_API. |
---|
| _Guardant_Code_API. |
---|
|
Anchor |
---|
| _Toc326167581 |
---|
| _Toc326167581 |
---|
|
Guardant Code API. Интерфейс прикладного программирования загружаемого кодаПри разработке загружаемого кода с большой вероятностью может возникнуть необходимость обращаться к ресурсам ключа, находящимся в области EEPROM (защищенным ячейкам, алгоритмам), или к таймеру. Поэтому был разработан специальный интерфейс прикладного программирования Guardant Code API (см. Справочную систему Guardant API, файл GrdAPI.chm) Библиотека этого API содержит большинство функций Guardant API, адаптированных для использования из среды загружаемого кода.
Основной нюанс при работе с Guardant Code API состоит в том, что хэндл защищенного контейнера в загружаемом коде теряет смысл, поскольку этот код, во-первых, имеет доступ только к одному ключу, а во-вторых, не существует ситуации конкурентного доступа к ресурсам ключа из разных потоков одного приложения и из разных приложений.
Вместе с тем, функциям внутренного API загружаемого кода передается параметр типа HANDLE. Это сделано для соблюдения единообразия и удобства отладки загружаемого кода.
Guardant Code API поддерживает основные функции Guardant API, связанные с хранением данных и работой с алгоритмами.
Кроме того, в API загружаемого кода существует возможность вызывать криптографические алгоритмы, не используя дескрипторы, а напрямую, подобно тому, как в Guardant API вызываются программно-реализованные алгоритмы. Для этого вместо числового имени ячейки, содержащей дескриптор, указывается специальное зарезервированное имя алгоритма.
Если в загружаемом коде присутствуют функции Guardant API (например, в ключ переносится алгоритм, который раньше защищался ключами Guardant), то для большинства этих функций существуют аналоги в Guardant Code API, и портирование будет заключаться в смене префикса c GrdXXX на GcaXXX или GccaXXX.
...
Важная информация
1. Подробную информацию по функциям внутреннего Guardant Code API см. в Справочной системе по Guardant API (файл GrdAPI.chm).
2. Поскольку в Guardant Code не реализован алгоритм GSII64 и производные от него (HASH64, RAND64 и т. д.), возможно придется немного переработать существующую схему защиты на использование алгоритмов AES128 для шифрования и SHA256 для хэширования. Все остальные возможности предыдущих поколений ключей присутствуют и в Guardant Code.
Anchor |
---|
| _Toc244330129 |
---|
| _Toc244330129 |
---|
|
Anchor |
---|
| _Сервисные_функции_GrdAPI |
---|
| _Сервисные_функции_GrdAPI |
---|
|
Сервисные функции GrdAPI для работы с загружаемым кодомЗагружаемый код, в отличие от ячеек с данными и дескрипторов аппаратных алгоритмов, хранится в другой области памяти ключа, для которой используются иные принципы адресации. Поэтому для загрузки кода в память ключа, его выполнения и других операций существуют специальные функции Guardant API.
Для записи в ключ загружаемый код преобразуется в файл специального формата GCEXE (Guardant Code Executable), обеспечивающего защиту от подделки, подмены и анализа. Эта защита обеспечивается шифрованием криптостойкими алгоритмами и ЭЦП. Такая защита делает возможным безопасное обновление загружаемого кода в ключе, в том числе при пересылке файлов по открытым каналам и через сети общего пользования.
Файл формата GCEXE генерируется на основе данных дескриптора загружаемого кода, скомпилированного кода и map-файла утилитой программирования ключей GrdUtil.exe. Однажды сгенерированный файл может быть использован для записи в тиражируемые ключи той же утилитой. Если предпродажное программирование осуществляется при помощи собственных специально разработанных инструментов, файл с загружаемым кодом может быть записан в ключ при помощи функции GrdCodeLoad().
Список сервисных функций Guardant API:
...
Подробнее см. в Справочной системе по Guardant API (файл GrdAPI.chm).
Anchor |
---|
| _Toc244330130 |
---|
| _Toc244330130 |
---|
|
Anchor |
---|
| _Toc326167582 |
---|
| _Toc326167582 |
---|
|
Компиляция загружаемого кода Anchor |
---|
| _Toc244330131 |
---|
| _Toc244330131 |
---|
|
Инсталляция и настройка компилятора GCCДля компиляции загружаемого кода используются компилятор и линкер GCC, стандартная библиотека С для встраиваемых систем newlib, утилита make и несколько сервисных утилит.
Существует два варианта использования GCC:
...
После выполнения этих шагов компилятор полностью готов к работе. Дальнейшая работа с ним будет выполняться посредством вызова утилиты make на заранее созданных и настроенных makefile.
Anchor |
---|
| _Toc244330132 |
---|
| _Toc244330132 |
---|
|
Общие сведения о компиляции и сборкеВсе инструкции для компилятора и линкера, а также команды для обработки скомпилированного кода, содержатся в конфигурационном файле утилиты make (имя этого файла по умолчанию - makefile).
Соответственно, для компиляции приложения необходимо:
- Отредактировать настройки в секции Main Configuration внутри makefile:
...
Важная информация
В makefile нельзя использовать символ «\» и пути с пробелами. В качестве разделителя необходимо указывать «/». Чтобы избежать пробелов, можно задавать относительные пути, либо копировать необходимые для компиляции
файлы в отдельную директорию.
Anchor |
---|
| _Toc244330133 |
---|
| _Toc244330133 |
---|
|
Команды утилиты makeПриложение собирается при помощи GNU-утилиты make, которая использует конфигурационный файл с именем makefile.
Для утилиты make доступны следующие команды:
1. Сборка проекта
Если конфигурационный файл имеет имя по умолчанию (makefile):
...
Если конфигурационный файл имеет имя, отличное от имени по умолчанию:
2. Удаление всех файлов, создаваемых при сборке(файлы, создаваемые при make template не удаляются)
или
3. Создание шаблона приложения
или
make -f confname template |
Важная информация
Если в сгенерированные при создании шаблона проекта файлы Startup.S и rom.ld были внесены изменения, они будут потеряны при повторном создании шаблона!
Полная пересборка приложения
или
make -f confname clean make -f confname all |
Пересборка может требоваться при изменении уровня оптимизации и при добавлении новых файлов (см. следующий раздел).
Важная информация
Если в системе одновременно с GCC установлены другие компиляторы, использующие собственные утилиты make (например, Borland C), при вызове make следует указывать полный путь, поскольку пути к другим утилитам make могут быть прописаны в переменной среды PATH. Можно придумать и иные способы дифференциации.
Anchor |
---|
| _Toc244330134 |
---|
| _Toc244330134 |
---|
|
Настройка универсального makefileУниверсальный makefile содержит секции настроек с параметрами:
...
- begin --------*
File main.c not found! Please check makefile (SRC, ASRC and CPPSRC values).
|
Anchor |
---|
| _Toc244330135 |
---|
| _Toc244330135 |
---|
|
Точка входа в приложениеПри старте приложения, в самом начале начинает исполняться код, находящийся в файле Startup.S. Он инициализирует стек и C-окружение (предварительно инициализированные переменные) и обнуляет неинициализированные переменные и область стека, при необходимости вызывает конструкторы глобальных объектов C. После этого он передает управление в приложение на C. Этот файл генерируется автоматически из универсального makefile.
По умолчанию точка входа в C-приложении на GCC имеет стандартное имя main. Прототип, однако, отличается от стандартного ANSI C и имеет следующий вид:
...
.global main … LDR R4, =main |
Anchor |
---|
| _Toc244330136 |
---|
| _Toc244330136 |
---|
|
Адресное пространствоВ микроконтроллерах на основе ядра CORTEX-M3, на которых построен ключ Guardant Code, имеется единое адресное пространство в 4Гб. Для загружаемого кода доступны следующие диапазоны адресов:
...
Диапазон используемых адресов указывается в makefile (параметры CFG_PROGRAM_ADDR, CFG_PROGRAM_SIZE, CFG_ RAM, CFG_RAM_SIZE). Задаваемые адреса должны быть кратны 0x8000 байт, и быть выровнены по границе 32768 байт.
Диапазон адресов, доступных загружаемому коду, описывается в соответствующем дескрипторе аппаратного алгоритма. GrdUtil автоматически заполняет соответствующие поля дескриптора информацией из файла *.bmap.
Поскольку по умолчанию под загружаемый код резервируется вся Flash-память и вся RAM, значения этих настроек без насущной необходимости изменять не нужно.
Anchor |
---|
| _Toc244330137 |
---|
| _Toc244330137 |
---|
|
Буферы ввода-выводаИмена буферов ввода и вывода в makefile могут быть разными, а могут и совпадать.
В случае, когда они совпадают, выделяется один буфер, который работает одновременно и на ввод, и на вывод. В C-коде буфер ввода-вывода может быть объявлен так:
...
При этом в параметрах CFG_INPUT_BUFFER_NAME и CFG_OUTPUT_BUFFER_NAME указывается значение iodata.
Если же используются раздельные буферы, то каждый из них объявляется в C-коде отдельно, и имеет собственное имя и размер.
При объявлении буферов допустимо использование любых типов данных, однако в случае структур рекомендуется добавлять в определение макрос ALIGNED, например:
extern struct { double x; … } iodata ALIGNED; |
Это указывает компилятору, что структура выровнена в памяти, и позволяет генерировать более эффективный код для доступа к полям структуры.
По умолчанию размер буфера ввода-вывода установлен равным 1024 байта. Для ввода-вывода в примерах используется единый буфер. Перед запуском загружаемого приложения данные в него помещаются, а после окончания работы возвращаются обратно в PC.
Максимальный суммарный размер буферов для ввода-вывода составляет 0x3F00 байт (16128 байт). Так же в объявлении переменной желательно указание макроса ALIGNED, который говорит компилятору, что буфер выровнен в памяти, и, в некоторых случаях, оптимизировать доступ к данной переменной.
Anchor |
---|
| _Toc239831217 |
---|
| _Toc239831217 |
---|
|
Anchor |
---|
| _Toc243829449 |
---|
| _Toc243829449 |
---|
|
Anchor |
---|
| _Toc244330138 |
---|
| _Toc244330138 |
---|
|
СтекРазмер программного стека для GCC указывается в makefile. За это отвечает параметр:
...
За счет этого происходит экономия памяти стека и увеличивается быстродействие кода.
Anchor |
---|
| _Toc239831220 |
---|
| _Toc239831220 |
---|
|
Anchor |
---|
| _Toc243829450 |
---|
| _Toc243829450 |
---|
|
Anchor |
---|
| _Toc244330139 |
---|
| _Toc244330139 |
---|
|
Anchor |
---|
| _Toc326167583 |
---|
| _Toc326167583 |
---|
|
Устройство загружаемого кодаПрямой перенос кода из исходного приложения может быть сопряжен с определенными трудностями. В общем случае, код, перенесенный в том же виде, как он существует в приложении, будет неработоспособен в электронном ключе. Поэтому код должен быть модифицирован и оптимизирован для выполнения на платформе CORTEX-M3. Желательно, чтобы этот код был написан заново и реализовывал функции, которых в ранних версиях приложения не было, либо эти функции должны быть видоизменены.
Anchor |
---|
| _Toc243829451 |
---|
| _Toc243829451 |
---|
|
Anchor |
---|
| _Toc244330140 |
---|
| _Toc244330140 |
---|
|
Параметры функции main()Прототип функции main() объявляется следующим образом:
...
DWORD func1(dwInDataLng, dwOutDataLng) { // Логика работы 1: return 101; } DWORD func2(dwInDataLng, dwOutDataLng) { // Логика работы 2: return 102; } DWORD func3(dwInDataLng, dwOutDataLng) { // Логика работы 3: return 103; }
DWORD main(DWORD dwInDataLng, DWORD dwOutDataLng, DWORD dwP1) { switch (dwP1) { case 0x01: return func1(dwInDataLng, dwOutDataLng); case 0x02: return func2(dwInDataLng, dwOutDataLng); case 0x03: return func3(dwInDataLng, dwOutDataLng); case 0x04: // ... default: return -1; } } |
Anchor |
---|
| _Toc243829452 |
---|
| _Toc243829452 |
---|
|
Anchor |
---|
| _Toc244330141 |
---|
| _Toc244330141 |
---|
|
Статические и глобальные переменныеПо возможности, переменные лучше объявлять глобально (хотя это и противоречит принципам функционального программирования), а не в теле функции, чтобы не передавать данные через стек. Этим экономится память стека и увеличивается быстродействие.
В загружаемом коде можно создать глобальные переменные, содержимое которых не будет обнуляться между вызовами. Такие переменные требуется объявлять с макросом NO_INIT:
...
Эти переменные являются аналогами статических переменных.
Польза от них может заключаться в возможности запоминания некоторых состояний загружаемого кода. Это делает анализ «черного ящика» гораздо более сложным.
Anchor |
---|
| _Toc239831223 |
---|
| _Toc239831223 |
---|
|
Anchor |
---|
| _Toc243829453 |
---|
| _Toc243829453 |
---|
|
Anchor |
---|
| _Toc244330142 |
---|
| _Toc244330142 |
---|
|
Возврат из загружаемого кодаВыход из приложения можно осуществлять следующим образом:
...
В примерах в качестве кода возврата с ошибкой используется значение -1. Для упрощения отладки можно возвращать значения макроса _LINE или пользоваться вызовом GcaExit(0, __LINE_). Этот способ поможет определить строку, на которой произошел выход из приложения. Оставлять в конечных версиях возвраты в данном виде нежелательно, так как это может дать дополнительную информацию для злоумышленника.
Для отладки можно, к примеру, использовать следующий макрос:
#define ASSERT(cond){if(cond)GcaExit(0,_LINE_);} |
ASSERT(x != 0); // Если x!=0, осуществит возврат из программы с указанием номера строки, в которой вставлен ASSERT.
Anchor |
---|
| _Toc239831225 |
---|
| _Toc239831225 |
---|
|
Anchor |
---|
| _Toc243829455 |
---|
| _Toc243829455 |
---|
|
Anchor |
---|
| _Toc244330144 |
---|
| _Toc244330144 |
---|
|
Использование арифметики с плавающей точкойЗа вычисления с плавающей точкой отвечает библиотека libm из комплекта GCC. Полное описание математических функций, доступных в ней, можно найти в документации к данной библиотеке. Для каждой функции имеется 2 варианта: обычный, для вычислений с двойной точностью (тип double), а также с приставкой «f», для вычислений с половинной точностью (тип float).
Anchor |
---|
| _Toc239831228 |
---|
| _Toc239831228 |
---|
|
Anchor |
---|
| _Toc243829456 |
---|
| _Toc243829456 |
---|
|
Anchor |
---|
| _Toc244330145 |
---|
| _Toc244330145 |
---|
|
Anchor |
---|
| _Toc326167584 |
---|
| _Toc326167584 |
---|
|
Отладка загружаемого кодаРазработка и первоначальная отладка загружаемого кода производится на компьютере. Для этого можно использовать любую IDE и отладчик языка С.
Основная проблема состоит в том, что отлаживать уже загруженный код затруднительно, поскольку нет возможности «залезть» отладчиком в контроллер ключа. Поэтому первоначально отлаживают сам алгоритм загружаемого кода.
Загруженный в ключ код имеет ограниченные возможности для трассировки. Например, нельзя вывести трассу на консоль или записать в файл. Однако некоторые средства все же есть. Для этой цели можно использовать функции управления светодиодом. Сигналы, подаваемые с его помощью, можно применять в качестве признаков прохождения тех или иных веток кода.
Как вариант, можно использовать принудительный возврат из загружаемого кода с соответствующим кодом возврата и передачей необходимых для отладки данных через буфер вывода.
Методы отладки кода такие же, как и при разработке на PC. Если в загружаемом коде используются вызовы Guardant Code API, то для отладки не нужно загружать этот код в ключ: можно использовать входящую в комплект разработчика отладочную библиотеку.
Anchor |
---|
| _Toc243829457 |
---|
| _Toc243829457 |
---|
|
Anchor |
---|
| _Toc244330146 |
---|
| _Toc244330146 |
---|
|
Описание отладочной библиотекиОтладочная библиотека представлена двумя частями:
...
Следует принимать во внимание, что ни сама отладочная библиотека, ни отладочный модуль не содержат логики работы функций. Они являются всего лишь своеобразным «туннелем», через который параметры вызова функций передаются в электронный ключ и возвращаются обратно.
Пример использования макроса DEBUGDLL_INIT:
main() { // Хэндл ключа, в котором запускается загруженный пользователем код: HADNLE hGrd;
// Инициализация API и подключение к электронному ключу ...
#ifdef DEBUG // Инициализация DLL DEBUGDLL_INIT(hGrd, 1); // hGrd – хендл открытого электронного ключа. // 1 – номер аппаратного алгоритма, в котором находится // загруженный отладочный модуль #endif
// Передача параметра hGrd необязательна. GcaGetRandom(0, &iodata[i]);
return 0; } |
Также стоит отметить, что функции GcaExit() и GcaLedOn()/ GcaLedOff() не могут работать в отладочном режиме. Первая – из-за того, что результат ее работы просто нельзя зафиксировать, а функции управления светодиодом – из-за того, что сразу после их вызова работа кода будет завершаться, при этом индикатор просто зажигается вновь.
Использование отладочной библиотеки демонстрируется в примере №19 (см. Краткая характеристика примеров).
Anchor |
---|
| _Toc243829458 |
---|
| _Toc243829458 |
---|
|
Anchor |
---|
| _Toc244330147 |
---|
| _Toc244330147 |
---|
|
Anchor |
---|
| _Toc326167585 |
---|
| _Toc326167585 |
---|
|
Загрузка кода в электронный ключДля загрузки кода в электронный ключ первоначально используется GrdUtil. При помощи этой утилиты создается дескриптор аппаратного алгоритма типа Загружаемый код.
В свойствах алгоритма указывается бинарный файл, который содержит скомпилированный загружаемый код. Этому файлу должен сопутствовать файл bmap, содержащий настройки адресов памяти.
Бинарный файл перед загрузкой должен быть преобразован в файл типа GCEXE (Guardant Code executable). Преобразование осуществляется в автоматическом режиме утилитой программирования ключей GrdUtil.
При выполнении преобразования GrdUtil генерирует ключевыепары:
...
Перед загрузкой бинарный файл зашифровывается на сеансовом ключе и подписывается ЭЦП. Это гарантирует возможность загрузки кода только разработчиком. При необходимости файл GCEXE можно сгенерировать таким образом, чтобы он мог быть загружен только в ключ с указанным ID. Эта возможность полезна для создания адресных обновлений, например – платных.
При записи данных в ключ первоначально записывается дескриптор алгоритма, а уже затем – файл GCEXE.
Однажды сгенерированный файл GCEXE может быть в дальнейшем записан и в другие ключи, содержащие соответствующие ключи шифрования и подписи. Для этого используется функция GrdCodeLoad().
Anchor |
---|
| _Toc243829459 |
---|
| _Toc243829459 |
---|
|
Anchor |
---|
| _Toc244330148 |
---|
| _Toc244330148 |
---|
|
Anchor |
---|
| _Toc326167586 |
---|
| _Toc326167586 |
---|
|
Отладка защищенного приложенияОтладке приложений, использующих загружаемый код, следует уделить особое внимание, поскольку поиск ошибок при работе с «черным ящиком» является непростым делом.
Очень важным является итоговое быстродействие загруженного кода. Если оно получается неудовлетворительным, требуется принять меры по приведению кода к обозначенным в начале этой главы требованиям.
Anchor |
---|
| _Toc243829460 |
---|
| _Toc243829460 |
---|
|
Anchor |
---|
| _Toc244330149 |
---|
| _Toc244330149 |
---|
|
Anchor |
---|
| _Toc326167587 |
---|
| _Toc326167587 |
---|
|
Дистанционное обновление загружаемого кодаПроблема обновления информации в ключах, уже находящихся у пользователей приложения, актуальна и для загружаемого кода. Рано или поздно в этот код может потребоваться внести изменения или исправления.
Для успешного обновления загружаемого кода необходимо выполнение следующих условий:
...
Для обновления загружаемого кода необходимо сгенерировать новый GCEXE-файл с обновленным кодом, зашифрованным и подписанным на соответствующих ключах.
Само обновление может производиться как при помощи технологии TRU, так и прямой загрузкой GCEXE-файла из защищаемого приложения функцией GrdCodeLoad().
При желании можно сделать процедуру обновления загружаемого кода «прозрачной» для пользователя. Тогда от него потребуется только получить обновление, поместить его рядом с исполняемым файлом приложения (или в специально для этого предназначенную директорию) и запустить приложение.