Коротко. Authenticode встраивает структуру PKCS #7 SignedData в таблицу в конце PE-файла Windows; подпись покрывает хеш файла, при вычислении которого пропускаются контрольная сумма, указатель на эту таблицу и сама таблица. С 1 июня 2023 года ключ подписи кода для публично доверенного сертификата должен генерироваться и использоваться в аппаратном модуле (токене, собственном HSM или облачной службе HSM с полным журналом аудита), поэтому с привычным файлом .pfx на сервере сборки сертификат больше не получить. Сертификаты, выпущенные с 1 марта 2026 года, действуют не более 460 дней, а служба Microsoft Artifact Signing выпускает сертификаты сроком на 72 часа, так что подпись остаётся действительной именно благодаря метке времени RFC 3161: всегда подписывайте с параметрами /fd SHA256 /tr <TSA URL> /td SHA256. EV-сертификаты больше не дают мгновенной репутации в SmartScreen. Проверяйте подпись командой signtool verify /pa /tw в Windows или osslsigncode verify в других системах.
Что именно подписывается
У PE-файла (.exe, .dll, .sys) в необязательном заголовке (optional header) есть каталог данных (data directory); его запись 4, таблица сертификатов (Certificate Table), содержит смещение в файле и размер списка структур WIN_CERTIFICATE, дописанного в конец файла. Для Authenticode у такой записи ревизия 0x0200 и тип WIN_CERT_TYPE_PKCS_SIGNED_DATA (0x0002), а внутри неё лежит структура PKCS #7 SignedData (формат PE).
Подписывается не сам файл, а структура SpcIndirectDataContent, в которой хранится хеш файла. Этот хеш не может покрывать саму подпись, поэтому, по словам Microsoft, при его вычислении «нужно исключить некоторые поля образа PE, например контрольную сумму и запись таблицы сертификатов в каталогах данных необязательного заголовка», а также саму таблицу сертификатов. Всё остальное — заголовки, секции и данные после последней секции — хешируется.
Образец — программа размером 1,6 МБ, собранная кросс-компиляцией на Go 1.27.3 (GOOS=windows GOARCH=amd64). Короткий скрипт на Python читает запись таблицы сертификатов до и после подписи:
file size 1651712, certificate table at offset 0, size 0
file size 1660952, certificate table at offset 1651712, size 9240
WIN_CERTIFICATE: dwLength 9240, wRevision 0x0200, wCertificateType 0x0002
Эти 9 240 байт — подпись, цепочка сертификатов и токен метки времени. Если вывести список идентификаторов объектов внутри извлечённой подписи, рядом с обычными частями CMS видны части, специфичные для Authenticode:
1 OBJECT :1.3.6.1.4.1.311.2.1.11
1 OBJECT :1.3.6.1.4.1.311.2.1.12
1 OBJECT :1.3.6.1.4.1.311.2.1.15
2 OBJECT :1.3.6.1.4.1.311.2.1.4
1 OBJECT :1.3.6.1.4.1.311.3.3.1
2 OBJECT :id-smime-ct-TSTInfo
2 OBJECT :messageDigest
2 OBJECT :signingTime
1.3.6.1.4.1.311.2.1.4 — это SpcIndirectDataContent, …2.1.15 — данные образа PE внутри него, …2.1.11 и …2.1.12 — соответственно тип заявления (statement type) и имя и URL программы, а 1.3.6.1.4.1.311.3.3.1 — токен метки времени RFC 3161, сохранённый как неподписанный атрибут, с типом содержимого id-smime-ct-TSTInfo.
Сертификаты и ключи в 2026 году
Публично доверенные сертификаты подписи кода регулируют Базовые требования к подписи кода (Code Signing Baseline Requirements, CSBR) CA/Browser Forum версии 3.11.0 от 16 июня 2026 года. Правила, которые меняют процесс сборки:
- Аппаратные ключи для всех. «С 1 июня 2023 года» закрытый ключ владельца сертификата должен генерироваться, храниться и использоваться в аппаратном криптографическом модуле, сертифицированном как минимум по FIPS 140-2 уровня 2 или Common Criteria EAL 4+ (§ 6.2.7.4.1). Допустимые варианты: аппаратный модуль, который вы обслуживаете сами, облачная служба ключей, чей HSM соответствует этим требованиям, а подписка «настроена так, чтобы журналировать все обращения, операции и изменения конфигурации», или служба подписи. Центр сертификации (CA) обязан это проверить, например отправив токен с заранее сгенерированным ключом или проверив аттестацию ключа (§ 6.2.7.4.2). До 2023 года это требование касалось только EV-сертификатов.
- Размеры ключей. Ключи RSA длиной не менее 3072 бит или ECDSA на кривых P-256, P-384 или P-521 (§ 6.1.5.2).
- Срок действия. Не более 460 дней для сертификатов, выпущенных 1 марта 2026 года или позже; до этой даты — 39 месяцев (§ 6.3.2).
- Службы меток времени (TSA). Сертификаты TSA действуют не более 135 месяцев, а их ключи можно использовать в течение 15 месяцев после
notBefore; токены меток времени с SHA-1 запрещены с апреля 2022 года.
Microsoft добавляет к этому собственные требования. Начиная с Windows 10 версии 1607 драйверы режима ядра должны быть подписаны самой Microsoft через Windows Hardware Dev Center, поэтому одного коммерческого сертификата недостаточно, чтобы драйвер загрузился (политика подписи драйверов). А EV больше не означает «никаких предупреждений SmartScreen»: на странице Microsoft о статусе функций распространения сказано, что после изменения Trusted Root Program в 2024 году «ни один тип сертификата (OV или EV) не даёт немедленного обхода»; репутация накапливается отдельно для каждого хеша файла и каждого удостоверения подписанта.
Управляемая служба Microsoft Artifact Signing (прежнее название — Trusted Signing) хранит ключи в HSM, которые обслуживает Microsoft, и выпускает сертификаты, которые «продлеваются ежедневно и действительны всего 72 часа» (управление сертификатами). Машину сборки покидает только дайджест. При таких коротких сертификатах подпись без метки времени через три дня становится бесполезной. Ещё две детали с той же страницы. Поскольку сертификат меняется каждый день, каждый профиль сертификатов получает постоянное собственное значение EKU (расширенного использования ключа) в ветке 1.3.6.1.4.1.311.97., к которому можно привязать доверие (например, в политиках управления приложениями). А CA этой службы исключаются из CCADB и больше не входят в Microsoft Trusted Root Program, но «остаются в списке доверия платформы Windows» (Windows Platform Trust List), поэтому Windows продолжает им доверять, а другие платформы могут и не доверять.
Подпись и проверка с osslsigncode
osslsigncode реализует Authenticode в Linux и macOS. В эксперименте (пакет Debian версии 2.14 в debian:forky-20261005-slim) мы создали частную тестовую иерархию из корневого CA, выпускающего CA и сертификата подписи кода RSA 3072 с EKU codeSigning, подписали программу и запросили метку времени у публичной TSA компании DigiCert:
osslsigncode sign -certs chain.pem -key codesign.key -h sha256 -n "Hello" -i "https://example.test/hello" \
-ts http://timestamp.digicert.com -in hello.exe -out hello-signed.exe
osslsigncode verify -CAfile root.pem -TSA-CAfile /etc/ssl/certs/ca-certificates.crt -in hello-signed.exe
В TSA отправляется только хеш подписи. Результат проверки (повторяющийся вывод цепочек сокращён):
Message digest algorithm : SHA256
Current message digest : ADEAAA5366F711E064A9D54630D1E45520727730ECA3B85423D7570B0D41F7A7
Calculated message digest : ADEAAA5366F711E064A9D54630D1E45520727730ECA3B85423D7570B0D41F7A7
Signer's certificate:
------------------
Signer #0:
Subject: CN=Example Software GmbH,O=Example Software GmbH,C=DE
Issuer : CN=Example Test Code Signing CA,O=Example Test
Serial : 20ADC33D7219F573885B44765077E96312F72F6D
Certificate expiration date:
notBefore : Oct 22 06:32:28 2026 GMT
notAfter : Nov 21 06:32:28 2026 GMT
Authenticated attributes:
Signing time: Oct 22 06:32:28 2026 GMT
Microsoft Individual Code Signing purpose
Message digest: DE669CE86A064FAEB4DFE8E43E14090CE5912FA036C527C13D5558B0BF110BB7
URL description: https://example.test/hello
Text description: Hello
Countersignatures:
Timestamp time: Oct 22 06:32:29 2026 GMT
Signing time: Oct 22 06:32:29 2026 GMT
Hash Algorithm: sha256
Issuer: CN=DigiCert Trusted G4 TimeStamping RSA4096 SHA256 2025 CA1,O=DigiCert\, Inc.,C=US
Serial: 084FDC334F7E454EDBC30F8FF9921835
...
Timestamp Server Signature CRL verification: ok
Timestamp Server Signature verification: ok
Signature verification time: Oct 22 06:32:29 2026 GMT
...
Signature verification: ok
Number of verified signatures: 1
Succeeded
«Current message digest» — хеш, сохранённый в подписи, а «Calculated» — хеш, заново вычисленный по файлу. Собственное «Signing time» подписанта — лишь утверждение, основанное на его часах, а под значением «Timestamp time» стоит подпись TSA, и именно на него опираются проверяющие стороны. Затем утилита osslsigncode проверила подпись на момент метки времени — в этом и состоит смысл всей конструкции.
Зачем нужна метка времени
Чтобы это увидеть, мы подписали ту же программу ещё раз, без -ts, а затем проверили оба файла так, как будто прошло 60 дней и срок действия 30-дневного тестового сертификата уже истёк (osslsigncode verify -time):
hello-signed.exe Signature verification time: Oct 22 06:32:29 2026 GMT Signature verification: ok Succeeded
hello-nots.exe ...:PKCS7_verify:certificate verify error:...:Verify error: certificate has expired Signature verification time: Dec 21 06:32:29 2026 GMT Error: certificate has expired ... Signature verification: failed Failed
С меткой времени проверка выполняется на доказанный момент подписи, поэтому подпись остаётся действительной и после того, как истечёт срок действия сертификата подписи (сертификат, которым DigiCert подписывает метки времени, в этом запуске и сам действителен до ноября 2037 года). Без метки проверка использует текущее время и перестаёт проходить в тот день, когда истекает срок действия сертификата, — та же ошибка certificate has expired, что и у любого TLS-клиента. Microsoft идёт дальше: если метка времени попадает в сроки действия и сертификата подписи, и сертификата TSA, подпись действительна, причём «остаётся действительной ещё долго после того, как истечёт срок действия сертификата подписи и сертификата TSA (если только ни один из них не отозван)».
Отзыв тоже привязан ко времени. Согласно CSBR, CA может указать в поле revocationDate сертификата подписи кода дату, когда сертификат впервые стал считаться недействительным, а при компрометации ключа — «самую раннюю дату предполагаемой компрометации»; при этом «программному обеспечению следует и дальше считать действительными объекты, содержащие метку времени, которая датирована раньше даты отзыва» (§ 4.9.6). Поэтому утёкший ключ делает недействительными только подписи с метками времени после компрометации, а не все релизы, которые вы когда-либо выпустили.
Что ломает подпись, а что нет
Два изменения файла, проверенные той же командой:
### one byte of code changed
Signature verification: failed
Message digest algorithm : SHA256
Current message digest : ADEAAA5366F711E064A9D54630D1E45520727730ECA3B85423D7570B0D41F7A7
Calculated message digest : E754D0A7C1AB0CA1716866C67046BA54D2C195CDCBA4A182D56DD5C57FCD37B6 MISMATCH!!!
Failed
### extra bytes inside the certificate table
appended 64 bytes inside the certificate table: size 9240 -> 9304
Warning: invalid PE checksum
Calculated message digest : ADEAAA5366F711E064A9D54630D1E45520727730ECA3B85423D7570B0D41F7A7
Timestamp Server Signature verification: ok
Signature verification: ok
Succeeded
Во втором файле размер в записи таблицы сертификатов и длина WIN_CERTIFICATE увеличены на 64 байта, а в появившееся место записаны данные. Поскольку таблица исключена из хеша, подпись по-прежнему проходит проверку; неверна только контрольная сумма PE, которая тоже исключена из хеша. Именно такая структура лежит в основе CVE-2013-3900: установщикам, которые читают конфигурацию из конца собственного файла, можно было подсунуть полезную нагрузку злоумышленника, не сделав подпись недействительной. Microsoft исправила это в 2013 году необязательной настройкой реестра EnableCertPaddingCheck, которую нужно включать самостоятельно, а в повторной публикации 2022 года сообщает, что «не планирует по умолчанию применять более строгий режим проверки». В январе 2022 года CISA внесло эту CVE в свой каталог известных эксплуатируемых уязвимостей (Known Exploited Vulnerabilities). Урок для разработчиков: никогда не читайте данные из области таблицы сертификатов собственного исполняемого файла и включите EnableCertPaddingCheck на машинах, которыми вы управляете.
В Windows: signtool
Эталонная реализация — SignTool из Windows SDK. Текущие версии требуют явно указывать алгоритмы дайджеста: «Для команды SignTool sign обязательны параметр алгоритма дайджеста файла (/fd) и параметр алгоритма дайджеста метки времени (/td)» (SignTool). Если сертификат лежит в хранилище Windows, а ключ — на токене, обычные команды такие:
signtool sign /n "Example Software GmbH" /fd SHA256 /tr http://timestamp.digicert.com /td SHA256 /d "Hello" MyApp.exe
signtool verify /pa /tw /v MyApp.exe
/nили/sha1выбирает сертификат; ключ остаётся в токене или HSM, и доступ к нему идёт через их CSP или KSP (/cspи/kcуказывают их явно)./trобращается к серверу RFC 3161; более старый/tиспользует устаревший протокол меток времени Authenticode.verify /paиспользует стандартную политику Authenticode; без этого параметра SignTool применяет политику для драйверов и отклоняет обычные подписи./twвыдаёт предупреждение, если у подписи нет метки времени, — этому параметру место в каждом пайплайне релиза./dlibи/dmdfподключают библиотеку, которая подписывает дайджест удалённо; такая библиотека есть у Artifact Signing, а Jsign с открытым исходным кодом (Apache-2.0, версия 7.6) делает то же самое в любой ОС, работая с AWS KMS, Google Cloud KMS, Azure Key Vault, токенами PKCS #11 и YubiKey.
На стороне проверки командлет PowerShell Get-AuthenticodeSignature возвращает статус и подписанта, а вкладка «Цифровые подписи» в Проводнике Windows показывает метку времени.
Подпись в пайплайне сборки
Когда ключи привязаны к оборудованию, на сервере сборки больше нет файла ключа. Работают три схемы:
- Облачная служба ключей: сгенерируйте ключ в AWS KMS, Google Cloud HSM или Azure Key Vault Premium, получите сертификат у CA, который принимает аттестацию ключей этой службы или её отчёт об аудите, и подписывайте дайджест удалённо с помощью Jsign или инструмента поставщика. Требование к журналу аудита входит в CSBR; в статье о подписи с облачным KMS показано, какие журналы по умолчанию выключены.
- Управляемая служба подписи, например Artifact Signing или собственная служба CA: служба хранит ключ и сертификат, а вы отправляете дайджесты.
- Токен на выделенном хосте подписи, доступном только из задания релиза. Это просто, но теперь одна-единственная машина оказывается на критическом пути.
В любом случае подписывайте только артефакты релиза, только из защищённой ветки и с меткой времени, а хеш файла и удостоверение подписанта записывайте в примечания к релизу, чтобы пользователи и инструменты EDR могли проверить, что именно вы выпустили.
Типичные ошибки
- Нет метки времени. Всё работает, пока не истечёт срок действия сертификата, а затем каждая установленная копия показывает недействительную подпись. С сертификатами на 72 часа или 460 дней этот день наступает скоро.
- SHA-1 где бы то ни было. Раньше без
/fdпо умолчанию использовался SHA-1; текущий SignTool в таком случае отказывается подписывать, но старые скрипты, скопированные с форумов, всё ещё содержат/fd sha1или/t. - Не та политика проверки.
signtool verifyбез/paобъявляет действительную коммерческую подпись недоверенной. - Сертификат без EKU
codeSigningили TLS-сертификат, используемый не по назначению. Проверяющие стороны его отклоняют, точно так же как OpenSSL выдаёт ошибку unsuitable certificate purpose. - Нет промежуточного сертификата. Включайте в подпись полную цепочку (
-certs chain.pemили промежуточный сертификат CA, установленный на хосте подписи), иначе результат проверки зависит от того, что окажется на машине пользователя. - Расчёт на то, что EV заглушит SmartScreen. Теперь репутация накапливается со временем; подписывайте каждый релиз одним и тем же удостоверением, чтобы она переходила на следующие релизы.
- Чтение данных из конца собственного подписанного файла (установщики, самораспаковывающиеся архивы): эта область подписью не покрыта.
Источники
- Базовые требования к подписи кода CA/Browser Forum (Code Signing Baseline Requirements) 3.11.0: § 4.9.6 (дата отзыва), § 6.1.5.2 (размеры ключей), § 6.2.7.4 (защита ключей), § 6.3.2 (срок действия)
- Microsoft: формат PE, таблица атрибутных сертификатов, SignTool, управление сертификатами Artifact Signing, политика подписи драйверов, статус функций распространения
- CVE-2013-3900 (MSRC) и запись в каталоге CISA KEV
- RFC 3161: протокол меток времени (Time-Stamp Protocol)
- Инструменты: osslsigncode 2.14, Jsign 7.6