Информационный портал технической поддержки Центра проектирования интегральных микросхем |
В микроконтроллере К1986ВК01GI реализованы три ядра: два Cortex-M4, и одно специальное - криптографический модуль (сопроцессор), построенный на базе Cortex-M0.
SWDIO – PC30;
SWCLK – PC31;
Но в «Release» варианте программы пользоваться отладчиком возможности нет, поэтому в первую очередь рассмотрим взаимодействие через шлюз. Шлюз представляет собой:
16 входных/выходных регистров (8 входных и 8 выходных. Следовательно, входной с одной стороны является выходным с другой),
2 FIFO данных: от открытого ядра к защищённому, и от защищённого ядра к открытому, шириной данных 32 бита и объёмом 16 слов.
Реализовано по 3 служебных регистра: для FIFO и для входных/выходных регистров. Они необходимы для контроля и настройки взаимодействия обмена данными по шлюзу. Подробная карта регистров блока шлюза представлена в таблице 1.
Таблица 1 – Карта регистров блока Шлюза
Открытая сторона M4 | Название регистра | Закрытая сторона M0 | ||
Доступ | Описание регистра | Описание регистра | Доступ | |
w/o |
Регистр записи во входное FIFO к защищённой стороне. |
OP_FIFO_OP_TO_SF | Регистр чтения из входного FIFO от открытой стороны. | r/o |
r/o |
Регистр чтения из выходного FIFO от защищённой стороны. |
OP_FIFO_SF_TO_OP | Регистр записи в выходное FIFO к открытой стороне. | w/o |
r/w | Регистр задания контрольных уровней заполненности FIFO. | OP_FIFO_LEVELS | Регистр задания контрольных уровней заполненности FIFO. | r/w |
r/w | Регистр задания маски разрешения источников запроса прерывания FIFO. | OP_FIFO_INT_MASK | Регистр задания маски разрешения источников запроса прерывания. | r/w |
r/w | Регистр индикации и очистки источников запроса прерывания FIFO. | OP_FIFO_INT_SOURCE | Регистр индикации и очистки источников запроса прерывания. | r/w |
r/w | Входной регистр. | OP_REG_0 | Выходной регистр. | r/o |
r/w | Входной регистр. | OP_REG_1 | Выходной регистр. | r/o |
r/w | Входной регистр. | OP_REG_2 | Выходной регистр. | r/o |
r/w | Входной регистр. | OP_REG_3 | Выходной регистр. | r/o |
r/w | Входной регистр. | OP_REG_4 | Выходной регистр. | r/o |
r/w | Входной регистр. | OP_REG_5 | Выходной регистр. | r/o |
r/w | Входной регистр. | OP_REG_6 | Выходной регистр. | r/o |
r/w | Входной регистр. | OP_REG_7 | Выходной регистр. | r/o |
r/o | Выходной регистр. | OP_REG_8 | Входной регистр. | r/w |
r/o | Выходной регистр. | OP_REG_9 | Входной регистр. | r/w |
r/o | Выходной регистр. | OP_REG_10 | Входной регистр. | r/w |
r/o | Выходной регистр. | OP_REG_11 | Входной регистр. | r/w |
r/o | Выходной регистр. | OP_REG_12 | Входной регистр. | r/w |
r/o | Выходной регистр. | OP_REG_13 | Входной регистр. | r/w |
r/o | Выходной регистр. | OP_REG_14 | Входной регистр. | r/w |
r/o | Выходной регистр. | OP_REG_15 | Входной регистр. | r/w |
r/o | Регистр статуса занятости входных регистров. | OP_REGS_BUSY | Регистр статуса занятости входных регистров. | r/o |
r/w | Регистр задания маски разрешения источников запроса прерывания REG_x. | OP_REGS_INT_MASK | Регистр задания маски разрешения источников запроса прерывания REG_x. | r/w |
r/w | Регистр индикации и очистки источников запроса прерывания REG_x. | OP_REGS_INT_SOURCE | Регистр индикации и очистки источников запроса прерывания REG_x. | r/w |
Алгоритм
Подготовить программу для работы сопроцессора M0.
Конвертировать программу в массив данных.
К этому массиву необходимо добавить Hash и наложить гамму.
После чего новый массив данных уже можно использовать в программе для ядра M4.
В основной программе для открытого ядра необходимо разрешить тактирование M0, после чего ядро M4 может приступить к передаче зашифрованных данных через шлюз в ядро M0.
Предварительно систему криптомодуля необходимо проинициализировать, а именно, записать серийный номер, записать гамму для расшифровки получаемой программы. Этот процесс осуществляется с помощью UART – загрузчика, выведенного на выводы:
A1 / UARTTX (CRPT_UARTTX);
B7 / UARTRX (CRPT_UARTRX).
если статус = 0x0000A55A, загрузчик приступает к процедуре инициализации OTP;
если статус = 0x75A9A55A, загрузчик понимает, что OTP инициализирована и приступает к процедуре обработки данных, полученных через шлюз от ядра M4.
Таким образом, есть 3 фазы работы встроенного загрузчика:
1. Фаза преинициализации - запись в одноразово-записываемую память (OTP) уникального серийного номера. Серийный номер имеет длину 8 байт. Запись серийного номера строго однократна. После завершения записи фаза жизненного цикла изменяется и этот функционал более никогда не будет доступен. Записанный серийный номер защищается контрольной суммой CRC32, ошибка контрольной суммы серийного номера при дальнейшей работе трактуется как угроза безопасности и приводит к блокированию дальнейшей работы защищённого ядра.
2. Фаза инициализации - загрузка и запись случайной последовательности (гаммы) в однократно программируемую память. После завершения записи фаза жизненного цикла изменяется и этот функционал более никогда не будет доступен.
3. Фаза работы - загрузчика защищённого ядра обеспечивает загрузку прошивки ядра из памяти незащищённой части через шлюз.
Выполняется проверка целостности серийного номера и его отправка в незащищённое ядро.
Выполняется проверка целостности серийного номера и гаммы, после чего серийный номер отправляется через шлюз в незащищённое ядро.
По готовности в регистрах шлюза выставляется флаг готовности. Прочитав этот флаг программное обеспечение незащищённого ядра должно загрузить в шлюз зашифрованную прошивку.
Загрузчик защищённого ядра проверяет целостность зашифрованной прошивки, сравнивая её CRC32. Если проверка выполнена успешна, то происходит расшифровывание прошивки в оперативной памяти путём её сложения с гаммой по модулю 2.
Целостность расшифрованной прошивки определяется путём расчёта и сравнения с расшифрованным значения хэш-функции RIPEMD-160.
Если целостность прошивки подтверждена, выставляется флаг готовности и управление передаётся расшифрованной прошивке в оперативной памятиРассмотрим каждую процедуру по отдельности.
По сути, данная процедура запускается всегда, когда OTP-память пустая. Первым этапом происходит настройка UART:
UART настраивается на скорость 9600 бод при условии, что частота тактирования микроконтроллера 8 МГц.
Рисунок 3 – Окно терминала, получение статуса готовности преинициализации
Ниже приведены все расшифровки статусов загрузчика:
// Base CM0 UART/GATE statuses
#define CM0_OK ((CM0_STATUS)0x3C5A0000)
#define CM0_FAIL ((CM0_STATUS)0xC3A50000)
// preinit-init-work statuses
#define CM0_PREINIT_READY ((CM0_STATUS)(0x5C0F | CM0_OK))
#define CM0_PREINIT_DONE ((CM0_STATUS)(0xC535 | CM0_OK))
#define CM0_PREINIT_FAIL ((CM0_STATUS)(0x533A | CM0_FAIL))
#define CM0_INIT_READY ((CM0_STATUS)(0xF05F | CM0_OK))
#define CM0_INIT_DONE ((CM0_STATUS)(0xC335 | CM0_OK))
#define CM0_INIT_FAIL ((CM0_STATUS)(0xFC3A | CM0_FAIL))
#define CM0_GAMMA_READY ((CM0_STATUS)(0x0A59 | CM0_OK))
#define CM0_WORK_READY ((CM0_STATUS)(0xF535 | CM0_OK))
#define CM0_WORK_DONE ((CM0_STATUS)(0xA50A | CM0_OK))
Затем ядро M0 ожидает приём по UART 8 байт серийного номера и 4 байта контрольной суммы CRC данного номера. После получения этих данных МК самостоятельно рассчитывает CRC от полученного серийного номера и сравнивает со значением, полученными по UART. В случае если значения не сошлись, то загрузчик возвращает статус CM0_PREINIT_FAIL. На рисунке 4 показан пример ввода серийного номера SN и некорректно рассчитанной к нему контрольной суммы CRC, в результате чего получен ожидаемый статус о неудачной процедуре преинициализации.
После сброса процессор, прочитав измененный статус во внутренней OTP-памяти, приступил к процедуре инициализации, сообщив об этом по UART, выдавая статус INIT_READY – рисунок 6. Дополнительно в 8 и 9 регистры шлюза загрузчик отправил серийный номер, записанный в его OTP-памяти во время преинициализации.
Рисунок 6 – Готовность процедуры инициализации
Затем необходимо передать загрузчику информацию о том, что гамма сформирована, то есть отправить статус GAMMA_READY. В ответ ожидается серийный номер, записанный в OTP-память на предыдущем этапе – рисунок 7. В случае же некорректной передачи статуса, в ответ поступит сообщение INIT_FAIL – рисунок 8.
Рисунок 7 - Выдача серийного номера, в случае получения статуса GAMMA_READY
Рисунок 8 - Выдача статуса INIT_FAIL
После успешной выдачи серийного номера микроконтроллер ожидает принять гамму размером 16364 байта. Это обязательное условие загрузчика. В рассматриваемом случае, к примеру, в качестве гаммы было выбрано число 42.
Если подключиться с помощью отладчика к ядру M0, то можно посмотреть, как выглядят записанные данные в OTP-памяти – рисунок 12.
Рисунок 12 - Отображение OTP-памяти ядра M0
Алгоритм взаимодействия между ядрами с помощью шлюза приведен выше на рисунке 2. В рамках демонстрации рассмотрим его на конкретном примере: будем накладывать гамму и HASH на прошивку для ядра M0 внутри программы для M4. Это отображено на измененной схеме алгоритма на рисунке 13.
К данной статье приложено два проекта:
1) Loader – проект с программой для ведущего ядра Cortex-M4, в котором разрешается тактирование криптоядра, а затем шифруется по гамма и передается через шлюз программа для исполнения ядром Cortex-M0;
2) CryptoHello – программа для исполнения ядром Cortex-M0.
Во втором проекте (CryptoHello) конфигурируется модуль UART и отправляется приветствие. Затем включается блок генератора случайных чисел, который генерирует число и отправляет его всё по тому же UART, а также дублирует его в 14 регистр шлюза.
После того как программа для Cortex-M0 была написана, необходимо сгенерировать файл ramCode.h. В приложенном проекте после процесса компиляции выставлена опция запуска файла формата *.bat для генерации файла ramCode.h (рисунок 14).
Рисунок 14 - Выставление настройки для генерации массива после процесса компиляции
То есть программа для исполнения ядром M0 была конвертирована в массив данных. Полученный файл ramCode.h необходимо добавить в проект для ядра Cortex-M4.
Теперь можно перейти к запуску первого проекта (Loader). Стоит вновь обратить внимание на разрешение тактирования криптоядра:
CLK_CNTR->KEY = 0x8555AAA1;
CLK_CNTR->CRPT_CLK |= (1<<28)|(1<<16); // HSE0
Затем в проекте происходит наложение HASH и Gamma на массив чисел из файла ramCode.h, который был добавлен в проект. После чего в основном цикле программы проверяется 15-ый регистр шлюза, куда криптоядро, как отмечено ранее, должно выставить статус WORK_READY.
crc = 0;
crc = crc32(crc, (uint8_t*)sn, 8); //sn - serial nember
uint32_t crc32(uint32_t ctx, const uint8_t* buf, uint32_t sz)
{
ctx = ctx ^ 0xFFFFFFFF;
while (sz--)
{
ctx = crc32_tab[(ctx ^ *(buf++)) & 0xFF] ^ (ctx >> 8);
}
return ctx ^ 0xFFFFFFFF;
}
#define OTP_BASE ((uint32_t)0x10100000U)
#define OTP_MEM_BASE (OTP_BASE)
#define OTP_MEM ((uint32_t *)OTP_MEM_BASE)
OtpData* const gOtpMem = (OtpData*)OTP_MEM;
typedef struct
{
OtpUserData UData; /*< User space */
OtpServiceData SData; /*< Service space */
uint32_t Gamma[SZ_GAMMA]; /*< Gamma arrea */
} OtpData;
typedef struct
{
uint32_t Special; /*< Special word */
uint32_t NotUsed[255]; /*< Testing arrea */
uint32_t User[12032]; /*< User space */
} OtpUserData;
#include "otpData.h"
int main (void)
{......
otpRead(&readOtpWord,&gOtpMem->UData.Special,1);
if ( readOtpWord == SecurityWord)
uartWrite ("\r\n JTAG is already blocked! \r\n", 30);
else
{
otpWrite(&gOtpMem->UData.Special, &SecurityWord, 1);
uartWrite ("\r\n JTAG blocked! \r\n", 15);
}
......
}
Временная блокировка SWD-интерфейса криптоядраSYS->CTRL &= ~(1<<7);
Сайт: | https://support.milandr.ru |
E-mail: | support@milandr.ru |
Телефон: | +7 495 221-13-55 |