Как устроен конвейер

Сборочный конвейер большой софтверной компании обычно состоит из множества виртуальных машин, управляемых оркестраторами сборки. В качестве последних часто используются TeamCity и Jenkins. В этом случае на виртуальных сборочных машинах установлены соответствующие сборочные агенты.

При разработке программного обеспечения для Windows есть необходимость подписывать исполняемые модули (файлы EXE и DLL), а также инсталляционные пакеты MSI с использованием закрытого ключа.  

Как всё было хорошо и просто раньше

До 2025 года такие закрытые ключи в комплекте с открытыми ключами и с сертификатами открытых ключей можно было приобрести у компании GlobalSign в виде отчуждаемого крипто-контейнера PFX в формате pkcs12, доступ к содержимому которого был защищён паролем.

При этом подпись файлов на виртуальной машине, работающей под управление сборочного агента, делается примерно так:

signtool.exe sign /f company-cert.pfx /p <password> /t http://timestamp.digicert.com /v "<файл, который должен быть подписан>"

Если вам необходимо было увеличить мощность конвейера в части выполнения операции подписи файлов, вы могли расположить крипто-контейнеры на нескольких виртуальных машинах. Файл PFX спокойно поддается копированию.

Как все стало в 2025 году

С 2025 года компания GlobalSign более не поставляет файлы крипто-контейнеров типа compamy-cert.pfx в формате pkcs12. Вместо этого поставляется защищенный электронный носитель Rutoken производства компании Актив-Софт, выполненный в форм-факторе usb-ключа, похожего на обыкновенную флешку.  Крипто-контейнер расположен во флеш-памяти usb-ключа. При этом из всех элементов содержимого крито-контейнера экспортировать можно лишь открытый ключ и его сертификат. Закрытий ключ от ключевой пары экспортировать невозможно. 

Объекты крипто-контейнера появляются в следующей последовательности:

  1. Сначала генерируется ключевая пара на защищенном электронном носителе Rutoken. При этом закрытый ключ является неизвлекаемым.

  2. Потом создается запрос на сертификат в удостоверяющий центр. В ответ удостоверяющий центр GlobalSign выпускает сертификат открытого ключа.

  3. Сертификат открытого ключа записывается на защищенный электронный носитель. Его можно извлечь из электронного носителя так же, как и открытый ключ.

Все готово для того, чтобы подписывать закрытым ключом файлы и проверять их подпись при помощи сертификата открытого ключа.

В чем сложности работы с Rutoken?

Защищённый электронный носитель Rutoken сам по себе является средством персональной ЭЦП. По конструкции он предназначен для подписи ответственным лицом нескольких десятков документов в день. Работа Rutoken начинается с операции логина, которая требует присутствия за компьютером оператора и ввода PIN-кода. Без этого получить доступ к содержимому крипто-контейнера невозможно. Конструктивно Rutoken изначально рассчитывался на работу в присутствии человека. То же самое относится к его зарубежному аналогу eToken.

Использование Rutoken на сборочном конвейере — это использование персональной системы ЭЦП на сервере без присутствия человека. Чтобы приспособить Rutoken к использованию на сборочном конвейере, нужно автоматизировать процесс ввода PIN-кода, либо этот PIN-код вообще отключить. К счастью, у Rutoken имеется функция кэширования однажды правильно введенного PIN-кода. Для ее включения следует отправить запрос в техническую поддержку компании Актив-Софт.

После включения функции кэширования PIN-код будет храниться на сервере в зашифрованном виде в файле C:\Users\<username>\AppData\LocalLow\Aktiv Co\Rutoken\pcache.txt

Таким образом система персональной ЭЦП может стать серверной системой.

Использование Rutoken вместе с signtool

Настройка совместной работы этого устройства и утилиты фирмы Microsoft оказалась не таким уж простым занятием.  Но давайте обо всем по порядку.

Защищенный электронный носитель:

  • Модель: Rutoken ECP 3.0 (3220) (Рутокен ЭЦП 3.0)

  • Версия прошивки: 65.04.30.02 (03)

  • Версия драйверов: 4.21.0.0

Окружение:

  • Токен подключен к хосту ESXi, который управляется через vSphere и проброшен в виртуальную машину.

  • Операционная система на виртуальной машине: Windows Server 2019 Standard.

Этап 1 — запуск вручную

Сначала мы проверили, как работает весь механизм при запуске из командной строки на целевой виртуальной машине.

Внутри виртуальной машины результаты следующее:

  • Токен виден в системе

  • Настроено кеширование PIN-кода

  • exe/msi-файлы успешно подписываются c помощью signtool из командной строки под учётной записью локального администратора.

Командная строка:

signtool sign /tr http://timestamp.globalsign.com/tsa/r6advanced1 /v /td SHA256 /debug /sha1 <SHA1> /fd SHA256 C:\test.exe

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

Проблемы отсутствуют, подпись файлов любого размера выполняется успешно.

Этап 2 — запуск из сборочного агента

  • Виртуальная машина с установленной внутрь операционной системой Windows Server 2019 регистрируется в качестве сборочного агента на сервере Teamcity.

  • В виртуальную машину устанавливается Teamcity Build Agent, который регистрируется как системная служба Windows, запускаемая под учётной записью локального администратора.

  • Запуск signtool переносится в отдельный build step в сборочной конфигурации на TeamCity.

Схема запуска signtool.exe в этом случае выглядит следующим образом:

  • Сервер TeamCity инициирует запуск сборки 

  • Служба Teamcity Build Agent на виртуальной машине под управлением Wndows 2019 запускает cmd-файл, который вызывает signtool.exe

Результаты:

  • Подпись exe- и dll-файлов выполняется успешно. Максимальный размер подписываемых файлов  —до 5MB.

  • Подпись MSI-файлов успешно выполняется лишь примерно в 20% случаев. Типовой размер MSI- файлов — от 70 MB до 700 MB.

При этом те же самые MSI файлы любого размера успешно подписываются при запуске signtool.exe из командной строки. 

В случае ошибок signtool.exe генерирует одно и тоже сообщение: The specified private key container was not found.

Полный текст:

The following certificates were considered:

     Issued to: Infowatch Laboratory LLC

     Issued by: GlobalSign GCC R45 CodeSigning CA 2020

     Expires:   Tue Apr 25 15:23:20 2028

     SHA1 hash: <SHA1>

  After EKU filter, 1 certs were left.

 After expiry filter, 1 certs were left.

 After Hash filter, 1 certs were left.

 After Private Key filter, 1 certs were left.

 The following certificate was selected:

     Issued to: Infowatch Laboratory LLC

     Issued by: GlobalSign GCC R45 CodeSigning CA 2020

     Expires:   Tue Apr 25 15:23:20 2028

     SHA1 hash: <SHA1> 

 Done Adding Additional Store

 SignTool Error: The specified private key container was not found.

Решили поэкспериментировать

Сценарии, которые были проверены при запуске signtool из-под сервиса TeamCity Build Agent:

  • Последовательная подпись двух файлов в рамках выполнения одной сборки:

signtool <…> C:\test1.msi
signtool <…> C:\test2.msi

Результат:

Подпись test1.msi иногда выполнялась успешно
Подпись test2.msi всегда заканчивалась ошибкой. 

  • Запуск signtool в цикле с паузами между итерациями длительностью несколько минут

Результат: иногда подпись выполнялась успешно после 5–10 неудачных попыток.

  • Перезагрузка виртуальной машины.

Результат: После перезагрузки ВМ первый запуск signtool мог выполниться успешно, но в большинстве случаев завершался с ошибкой.

  • Длительная (несколько часов) пауза после неудачной попытки

Результат: Последующий запуск signtool.exe, как правило, также не был успешен

Еще поэкспериментировали

Альтернативные варианты запуска из-под системной службы Windows для исключения влияния конкретной реализации TeamCity Build Agent

  • Запустили signtool.exe с помощью PsExec. Получили аналогичные проблемы. Появились ошибки при подписи MSI-файлов.

  • Написали собственный простенький вариант системной службы Windows на WinApi c вариациями запуска signtool.exe c помощью CreateProcess / CreateProcessAsUser / WTSQueryUserToken+ / etc. Получили аналогичные проблемы. Появились ошибки при подписи MSI-файлов.

  • Манипуляции с параметрами запуска системной службы Windows ни на что не повлияли (Local System /interactive/etc.)

В рамках экспериментов также менялись права на файл, где кэшируется PIN-код C:\Users\<username>\AppData\LocalLow\Aktiv Co\Rutoken\pcache.txt

Это тоже к исчезновению ошибки не привело.

Решение проблемы, наконец, было найдено

Из цепочки вызовов, которая приводила к запуску signtool.exe, полностью исключили системную службу Windows. Сделали это так:

  1. На виртуальной машине Windows Server 2019 настроили auto logon от имени пользователя с правами администратора. https://learn.microsoft.com/en-us/troubleshoot/windows-server/user-profiles-and-logon/turn-on-automatic-logon 

  2. Через группу Автозапуск (Start Up) для автоматически залогиненного пользователя с правами администратора запустили TeamCity Build Agent как обычное консольное приложение. Он удачно связался с сервером TeamCity и начал работать.

Результаты: после перехода на этот вариант при помощи Rutoken ЭЦП 3.0 стали успешно подписываться дистрибутивы с десятками исполняемых файлов.

Техническая реализация

TeamCity Build Agent — это не нативное консольное приложение. Сам агент написан на языке Java, представляет из себя *.jar-файл и запускает внутри Java-машины. 

Для работы агента в консольном режиме нужно дополнительно настраивать пути к файлам конфигурации, classpath и прочие параметры запуска Java-машины. Чтобы не тратить на это время, можно запустить готовый BAT-файл “c:\BuildAgent\bin\agent.bat” start.

Заключение

Всего в 2025 году на сборочном конвейере при помощи вышеуказанной методики было успешно подписано 22 тысячи файлов.

Авторы статьи

Андрей Петров

руководитель отдела DevOps

Юрий Жмеренецкий

DevOps-инженер