Как устроен конвейер
Сборочный конвейер большой софтверной компании обычно состоит из множества виртуальных машин, управляемых оркестраторами сборки. В качестве последних часто используются 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-ключа. При этом из всех элементов содержимого крито-контейнера экспортировать можно лишь открытый ключ и его сертификат. Закрытий ключ от ключевой пары экспортировать невозможно.
Объекты крипто-контейнера появляются в следующей последовательности:
Сначала генерируется ключевая пара на защищенном электронном носителе Rutoken. При этом закрытый ключ является неизвлекаемым.
Потом создается запрос на сертификат в удостоверяющий центр. В ответ удостоверяющий центр GlobalSign выпускает сертификат открытого ключа.
Сертификат открытого ключа записывается на защищенный электронный носитель. Его можно извлечь из электронного носителя так же, как и открытый ключ.
Все готово для того, чтобы подписывать закрытым ключом файлы и проверять их подпись при помощи сертификата открытого ключа.
В чем сложности работы с 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.msisigntool <…> 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. Сделали это так:
На виртуальной машине Windows Server 2019 настроили auto logon от имени пользователя с правами администратора. https://learn.microsoft.com/en-us/troubleshoot/windows-server/user-profiles-and-logon/turn-on-automatic-logon
Через группу Автозапуск (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-инженер
