Ещё раз про аппаратные ключи GPG за копейки


В февралe 2020 года на Хабре появилась увлекательная статья про преобразование программатора ST-Link v2 в аппаратный ключ шифрования. Уже тогда в комментариях появились жалобы на то, что результат не удаётся повторить, но они остались без ответа.


За прошедшее время до меня дошла пара посылок с Aliexpress и теперь, самостоятельно пройдя весь путь, я попытаюсь представить более или менее полную инструкцию с комментариями, которая поможет неспециалисту перепрошить микропроцессор в китайском клоне ST-Link v2. Постараюсь не повторять уже известное, поэтому про пайку контактов и использование GPG с аппаратным ключом смотрите в исходной статье.


Аппаратная часть


Первое, на что нужно обратить внимание ещё до покупки, — это комментарии к товару на Aliexpress. Скорее всего, кто-нибудь уже написал про марку микропроцессора из серии STM32***, который установлен в изделие у данного конкретного продавца. Впрочем, мой опыт показал, что комментарии не помогают, и даже у одного продавца могут попадаться программаторы с разной начинкой.


Главное отличие микропроцессоров с нашей точки зрения — это объём флэш-памяти размещённой на кристалле. На одном из форумов попадалось мнение, что китайцы могут выпустить программатор на дешёвом процессоре без аппаратной поддержки USB и эмулировать USB программно, дёргая одну из ножек процессора, но это, маловероятно, поскольку требует существенной доработки исходного кода программатора.


Оригинальный ST-Link v2 от фирмы STMicroelectronics базировался на чипе STM32F103 той же фирмы, имеющего 128 Кбайт флэш-памяти.


В первой посылке мне пришёл программатор на китайском клоне STM32F103CKS32F103C8*. В интернете не очень много информации по этому чипу. Его обзывают то CS32F то CKS32F. Даже даташит на китайском куда-то пропал, так что сообщаю, что на моём чипе всего 64 Kбайт флэш-памяти. Вроде бы, процессоры CKS32F103CB* имеют 128 Kбайт флэша, но это только слухи из интернета.


Во второй посылке оказались изделия на основе STM32GC102CB. Что это такое, похоже, не знает никто, но в нём опять 64 Kбайт флэш-памяти (и, к счастью, есть аппаратный USB).


Фокус в том, что прошивка программатора в минимальной конфигурации укладывается в эти 64 Кбайт и китайцы явно пытаются сэкономить копеечку. Про обновление прошивки ST-Link v2 в качестве программатора можно почитать в статье на хабре.


Программное обеспечение


Проще всего собрать комплект ПО под каким-нибудь Linux, основанном на Debian. В моём случае это был antiX Linux и репозитории Debian 9 Stretch.


make и git у меня были, так что мне хватило установки специфических пакетов:


sudo apt install gcc-arm-none-eabi
sudo apt install libnewlib-arm-none-eabi
sudo apt install openocd

Плюс к этому, установилось несколько зависимостей.


Почему-то мне не удалось найти пакеты c Arm Embedded Toolchain в стандартных репозиториях CentOS. Может искал не по тем ключевым словам.


Под Windows всё придётся настраивать самостоятельно:



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


Проверка и настройка программатора и целевой платы


Ещё раз напоминаю, что для дальнейших шагов вам потребуется подпаять проводки от программатора на целевую плату, взятую из другого программатора. Смотрим оригинальную статью.


Хочу обратить внимание на то, что у разных программаторов разная разводка контактов. Получив вторую посылку я почти убедил себя, что пришли бракованные программаторы, просто потому, что переткнул пучок проводов из одного программатора в другой, не меняя их раскладку. В интернете попадаются жалобы, что на плате идёт одна маркировка выводов, а на корпусе нарисована другая. Маркировка на плате более достоверна.


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


Для начала нам необходимо разблокировать доступ к флэш-памяти на целевой плате, поскольку изначально он закрыт и на запись и на чтение (для сохранения в тайне содержимого текущей прошивки).


Ситуация 1: целевая плата с микросхемой ST32F1**STM32GC102CB)


$ openocd -f interface/stlink-v2.cfg -f target/st32f1x.cfg -c 'init; reset halt; stm32f1x unlock 0; reset halt;exit'
...
Info : device id = 0x20036410
Info : flash size = 64kbytes
stm32x unlocked.
INFO: a reset or power cycle is required for the new settings to take effect.

Ситуация 2: целевая плата с микросхемой CKS32F1**


$ openocd -f interface/stlink-v2.cfg -f target/st32f1x.cfg -c 'init; reset halt; stm32f1x unlock 0; reset halt;exit'
...
Info : STLINK v2 JTAG v29 API v2 SWIM v7 VID 0x0483 PID 0x3748
Info : using stlink api v2
Info : Target voltage: 3.174295
Warn : UNEXPECTED idcode: 0x2ba01477
Error: expected 1 of 1: 0x1ba01477

Файлы interface/stlink-v2.cfg и target/st32f1x.cfg — это скрипты на языке OpenOCD, настраивающие параметры программатора и целевой платы. Разработчики китайского клона назначили ID своего процессора 0x2ba01477, а у STM321F1*** было 0x1ba01477. В принципе, в стандартной поставке OpenOCD есть несколько конфигураций с нужным ID, но я решил, что во всём остальном клон должен быть похож именно на STM321F1**, и просто скопировал файл конфигурации к себе и поправил ID в команде set _CPUTAPID 0x1ba01477.


 mkdir ~/.openocd/{target}
 cp /usr/share/openocd/scripts/target/stm32f1x.cfg ~/.openocd/target/cks32f1x.cfg
 sed -i 's/0x1ba01477/0x2ba01477/' /.openocd/target/cks32f1x.cfg

В дальнейшем, при прошивке плат с процессором CKS32F1** я просто указываю опцию -f target/cks32f1x.cfg.


Теперь можно проверить объём памяти. Можно, например, сгенерировать случайный файл размером 128 Кбайт и попытаться записать его во флэш-память. Тогда OpenOCD выдаст ошибки примерно такого вида:


Error: checksum mismatch - attempting binary compare
diff 0 address 0x08010000. Was 0x00 instead of 0x7d
diff 1 address 0x08010001. Was 0x50 instead of 0x40

Я просто попытался разблокировать второй банк памяти:


$ openocd -f interface/stlink-v2.cfg -f target/cks32f1x.cfg -c 'init; reset halt; stm32f1x unlock 1; reset halt;exit'
...
Error: flash bank 1 does not exist
...

Если моя методика неверна — пишите, перепроверю чипы другим способом.


Компиляция прошивки GNUK


Когда мы определились с объёмом флэш-памяти на чипе, можно выбрать версию прошивки GNUK, которая и обеспечивает функциональность нашей платы как аппаратного ключа шифрования.


Если удариться в историю, то в 2003 году компания g10code, созданная ведущим разработчиком GnuPG, написала спецификации протокола для взаимодействия программы, соответствующей спецификации OpenPGP, с аппаратным ключом шифрования, реализованным по стандарту смарткарты ISO/IEC 7816. Ещё одна компания — ZeitControl написала проприетарный код, реализующий необходимые алгоритмы, для своей Card OS (и своей смарт-карты). Нынче получившуюся игрушку для гиков можно купить в интернет магазине FLOSS-Shop под названием OpenPGP Smart Card по несколько завышенной цене в 17,90 €. Не забудьте, что кроме смарт-карты, вам потребуется купить ещё и считыватель для неё.


Несколько позже в игру вступила некоммерческая организация Free Software Initiative of Japan — этакая национальная ячейка FSF. Именно они разработали прошивку GNUK для процессоров STM32*, которая эмулирует считыватель смарт-карт со вставленной в него OpenPGP Smart Card.


На настоящий момент существует две стабильные версии GNUK — 1.0 и 1.2, отличающиеся базовой ОС и набором алгоритмов шифрования. Я бы порекомендовал брать их из дебиановского репозитория, где они находятся в бранчах STABLE-BRANCH-1-0 и STABLE-BRANCH-1-2 соответственно.


В части шифрования обе версии базируются на библиотеке PolarSSL, а в аппаратной части версия 1.0 базируется на ChibiOS_2.0.8, а версия 1.2 на библиотеке chopstx.


Для нас важно, что версия 1.0 компилируется в исполняемый код объёмом 62 Кбайт, а версия 1.2 — в 112 Кбайт.


Итак, если у нас есть 128 Кбайт флэша, то компилируем версию 1.2


git clone https://salsa.debian.org/gnuk-team/gnuk/gnuk.git
cd gnuk
git checkout  STABLE-BRANCH-1-2
# подтягиваем библиотеку 'chopstx'
git submodule update --init
cd src
# --target определяет ножки процессора, на которые привязан светодиод и управление USB
# Значение --target по умолчанию - FST_01 - не совместимо с ST-LINK v2
# --vidpid="234b:0000" соответствует USB считывателю смарт-карт
# --enable-factory-reset - возможность программного обнуления ключа при забытом PIN
./configure --target=ST_DONGLE --vidpid="234b:0000" --enable-factory-reset
make

Если у нас есть только 64 Кбайт флэша, то мы компилируем версию 1.0


git clone https://salsa.debian.org/gnuk-team/gnuk/gnuk.git
cd gnuk
git checkout STABLE-BRANCH-1-0
cd src
# На удивление, значение по умолчанию в этой версии - OLIMEX_STM32_H103 - совместимо с ST-LINK v2
# --enable-keygen - включает внутреннюю генерацию секретного ключа
./configure --vidpid="234b:0000" --enable-keygen
make

Возможно, в этот момент вы получите множество однотипных сообщений об ошибках сборки:


usb_lld.o: In function `usb_lld_set_data_to_recv':
usb_lld.c:(.text.usb_lld_set_data_to_recv+0x0): multiple definition of `usb_lld_set_data_to_recv'
main.o:/mnt/f/DocSasha/devzone/gnuk/src/usb_lld.h:136: first defined here
...

Проблема в том, что компилятор по непонятной причине игнорирует директиву inline в заголовочном файле usb_lld.h и компилирует по экземпляру функции usb_lld_set_data_to_recv в каждом модуле, в котором этот заголовочный файл использовался.


Для решения проблемы достаточно заменить inline-функцию на эквивалентный макрос #define и снова запустить make. Для правки можно использовать patch, а можно просто удалить строки, помеченные минусом, и добавить строку, помеченную плюсом.


--- a/src/usb_lld.h
+++ b/src/usb_lld.h
@@ -131,10 +131,7 @@ extern void usb_lld_set_feature (uint8_t feature);

 extern void usb_lld_set_data_to_send (const void *p, size_t len);

-extern inline void usb_lld_set_data_to_recv (void *p, size_t len)
-{
-  usb_lld_set_data_to_send ((const void *)p, len);
-}
+#define usb_lld_set_data_to_recv(p, len)  usb_lld_set_data_to_send ((const void *)p, len)

 extern void usb_lld_prepare_shutdown (void);
 extern void usb_lld_shutdown (void);

Заливаем прошивку в чип. Не забываем выбрать правильный cfg файл и помним, что в версии 1.2 образ прошивки лежит в src/build/gnuk.elf, а в версии 1.0 в src/gnuk.elf


# загружаем прошивку
openocd -f interface/stlink-v2.cfg -f target/stm32f1x.cfg -c 'program build/gnuk.elf verify reset exit'
# и запрещаем доступ к флэшу
openocd -f interface/stlink-v2.cfg -f target/stm32f1x.cfg -c init -c "reset halt" -c "stm32f1x lock 0" -c reset -c exit

Поскольку у меня все процессоры с 64 Кбайт флэш-памяти я не смог проверить, надо ли применять команду stm32f1x lock 1, чтобы заблокировать доступ ко второму банку памяти.


Заключение


Ну вот и всё. У меня получилось. Надеюсь, что и у вас получится.


Прочие ссылки


Комментарии 17

  • НЛО прилетело и опубликовало эту надпись здесь
      0
      Идея классная, но Юзабилити нулевая.
      Почему нет удобной утилиты для шифрования файлов папок с помощью этого ключа или ключа хранящегося в этом STM ключе?
      кликнул на файл типа архивный если ключ не вставлен в ЮСБ появилось окно с просьбой вставить и ввести пин и если всё хокей открывается содержимое архива- папок пока ключ вставлен все изменения файлов обновляются в шифрованном архиве. без варганов и бубнов с командной строкой…
        0

        Veracrypt поддерживает аппаратные ключи, но там используется протокол PKCS #11. Пишут, что OpenPGP Card может обеспечить PKCS #11 через промежуточный слой OpenSC. Сам не проверял, но если PKCS #11 заработает, то этот ключ можно будет использовать совместно и с Veracrypt и с огромным количеством иных приложений.

          0
          На самом деле, есть довольно много недорогих PKCS11-совместимых ключей отечественного производства (в России это RuToken, в Украине — Avtor)
            0

            В данном случае речь идёт об игре "сделай сам". Понятно, что серьёзные люди для серьёзных задач покупают проверенные коммерческие решения. Здесь же речь идёт о том, как почти бесплатно сделать что-то почти настоящее.

          0
          На самом деле, во многих линукс-дистрибутивах поддерживается из коробки LUKS шифрование диска с использованием аппаратных ключей Yubikey. Для того, чтобы запустить ОС, нужно вставить yubikey и ввести пин-код. Пакет насколько я помню называется yubikey-luks
          0
          Это оффтоп конечно, просто фото на
          КДПВ
          image
          напомнило. Видел на какой-то ещё советской плате что после того как проводки были к ней припаяны, они ещё несколько раз проходили через отверстия на плате- как шнурки в кроссовках. Там это было обусловлено тем что провода подвергались механическим напряжениям. Интересный способ, взял на вооружение- стоит копейки, просто подумать чутка при проектировании платы, и потом несколько дырочек просверлить.
            0
            Боюсь тут не актуально. Проводочки припаяны только ради одной прошивки данного донгла. Потом их отпаяют и всё…
            0

            При попытке прошить девайс получаю вот такое сообщение:


            ** Programming Started **
            auto erase enabled
            Info : device id = 0x20036410
            Info : flash size = 64kbytes
            Error: stm32x device protected
            Error: failed erasing sectors 0 to 54
            ** Programming Failed **
            shutdown command invoked

            Я понимаю, что микроконтроллер защищен от перезаписи, но как это победить?


            Команда


            openocd -f interface/stlink-v2.cfg -f target/cks32f1x.cfg -c 'init; reset init; stm32f1x lock 0; program ./gnuk.elf verify reset exit'

            не помогла :(

              0

              Поменяйте в команде разблокирования lock на unloc.

                0

                Сработало, спасибо! Кстати, оригинальную BluePill у меня gpg в упор не видит, а вот китайский клон, прошитый версией на 64кб, увидела.

                  0

                  Для BluePill надо указать другую опцию target:


                  ./configure --target=BLUE_PILL --vidpid=234b:0000
              +1

              Проще наверно взять bluepill и припаять к нему usb A чем гадать что там китайцы пришлют

                +1
                Для желающих использовать этот ключ на всякий случай напомню про относительно легкий способ считывания содержимого внутренней памяти:
                habr.com/ru/company/ntc-vulkan/blog/483732

                Не оставляйте его без присмотра надолго, если кому-то могут быть интересны ваши секреты)
                  +1
                  Автору респект! А то я тоже получил от китайцев устройство на чипе CKS32F103C8. А знаний по МК почти нет, как и времени на их получение.

                  Сначала создал ключ на самом устройстве (успешно), потом стал ковырять импорт ключа SSH (по этой статье). Делал так (там пришлось делать преобразование в 2 этапа, ssh -> pem ->gpg):
                  $ cp id_rsa id_rsa.tmp
                  $ chmod 600 id_rsa.tmp
                  $ ssh-keygen -p -N "" -m pem -f id_rsa.tmp
                  $ pem2openpgp temporary_id < id_rsa.tmp  | gpg --import
                  $ gpg -K
                  
                  sec   rsa4096 2020-04-15 [C]
                        XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
                  uid         [ неизвестно ] temporary_id
                  
                  $ gpg --edit-key  XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
                  gpg> keytocard
                  Действительно переместить первичный ключ? (y/N) y
                  Выберите, где хранить ключ:
                     (1) Ключ подписи
                  Ваш выбор? 1
                  Key does not match the card's capability.
                  Ваш выбор?
                  

                  И все…
                  Почему предлагается только ключ подписи?
                  Может ли быть так, что просто тупо не хватает памяти на МК? Там всего 64К.
                  Ну и главный вопрос — что делать? (Купить нормальный девайс не предлагать :) )
                    +1

                    У вас две проблемы.


                    Первая здесь: "sec rsa4096".


                    Прошивка GNUK 1.0. не поддерживает RSA ключи длиннее, чем 2048 бит. Более того, официальный FAQ от команды GNUPG не рекомендует использовать ключи rsa4096, поскольку при незначительном увеличении секретности они очень существенно повышают нагрузку на устройство шифрования.


                    Вторая проблема заключается в структуре ключей в GPG. Ключ состоит из мастер-ключа (он же ключ подписи) и подключей шифрования и аутенификации, которые являются расходным материалом, выпускаются по мере необходимости с ограниченным сроком действия и подписываются мастер-ключом. В ключе SSH, естественно, никакие подключей нет, поэтому единственный имеющийся ключ считается ключом подписи.


                    Попробуйте сгенерировать и сохранить на брелке ключ подписи, а потом импортировать ssh-ключ командой addcardkey.


                    Я не стал затирать свой ключ, но диалог выглядел так:


                    $gpg --edit-key 2A4F41ABEFF2949F  
                    gpg (GnuPG) 2.2.19; Copyright (C) 2019 Free Software Foundation, Inc.
                    This is free software: you are free to change and redistribute it.
                    There is NO WARRANTY, to the extent permitted by law.
                    
                    Секретный ключ доступен.
                    
                    sec  rsa2048/2A4F41ABEFF2949F
                              создан: 2020-04-16     годен до: никогда     назначение: C
                         доверие: неизвестно    достоверность: неизвестно
                    [ неизвестно ] (1). temporary_id
                    
                    gpg> addcardkey
                    Signature key ....: 5F3E 0DF0 FC07 5B76 3A7B  5775 A983 03F7 B6C6 FB1E
                    Encryption key....: 0651 C964 C454 0364 D854  9D58 AF38 C438 1272 BE24
                    Authentication key: 6A2B 1962 75F3 6364 F18B  DFF4 10A4 26A5 879E 9B45
                    
                    Выберите тип создаваемого ключа:
                       (1) Ключ подписи
                       (2) Ключ шифрования
                       (3) Ключ удостоверения личности
                    Ваш выбор? 3
                    
                    gpg: WARNING: such a key has already been stored on the card!
                    
                    Заменить существующий ключ? (y/N) y
                    gpg: error clearing forced signature PIN flag: Операция отменена
                    
                    gpg>
                    0
                    Автору спасибо! Подсказка про версию 1.0 очень помогла. Тоже получил от китайцев чип на 64к и долго пытался активировать второй блок. Правда вместо второго STM использую Pi, там же стразу и gnuk компилировал.

                    Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                    Самое читаемое