简短回答:Authenticode 把一个 PKCS #7 SignedData 嵌入 Windows PE 文件末尾的一张表中;签名覆盖的是文件的哈希,计算时跳过校验和、指向这张表的指针以及表本身。自2023年6月1日起,公开受信任的代码签名密钥必须在硬件模块(硬件令牌、本地部署的 HSM,或带完整审计日志的云 HSM 服务)中生成和使用,因此构建服务器上常见的那种 .pfx 文件已经无法让你拿到证书了。2026年3月1日起签发的证书最长有效 460 天,而 Microsoft 的 Artifact Signing 签发的证书只有 72 小时有效期,这使 RFC 3161 时间戳成为让签名保持有效的关键:签名时始终使用 /fd SHA256 /tr <TSA URL> /td SHA256。EV 证书也不再能立即换来 SmartScreen 信誉。验证时,在 Windows 上用 signtool verify /pa /tw,在其他系统上用 osslsigncode verify。
签名覆盖了什么
PE 文件(.exe、.dll、.sys)的可选头中有一个数据目录;其中 4 号条目即证书表(Certificate Table),记录着追加在文件末尾的 WIN_CERTIFICATE 结构列表的文件偏移和大小。Authenticode 使用的条目修订版本为 0x0200,类型为 WIN_CERT_TYPE_PKCS_SIGNED_DATA(0x0002),其中包含一个 PKCS #7 SignedData(PE 格式)。
被签名的内容不是文件本身,而是一个包含文件哈希的 SpcIndirectDataContent。这个哈希不能覆盖签名自身,所以用 Microsoft 的话说,它“必须排除 PE 映像中的某些字段,例如可选头数据目录中的校验和与证书表条目”,此外还要排除证书表本身。其余的一切,即各个头、各个节以及最后一节之后的数据,都会计入哈希。
我们的样本是一个用 Go 1.27.3 交叉编译的 1.6 MB 程序(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 字节就是签名、证书链和时间戳令牌。列出提取出的签名中的对象标识符,可以看到 Authenticode 特有的部分与常见的 CMS 部分并列其中:
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 分别是声明类型以及程序名称和 URL,1.3.6.1.4.1.311.3.3.1 则是作为未签名属性存放的 RFC 3161 时间戳令牌,其内容类型为 id-smime-ct-TSTInfo。
2026年的证书与密钥
公开受信任的代码签名证书受 CA/Browser Forum 的《代码签名基线要求》(CSBR)约束,版本为2026年6月16日发布的 3.11.0。以下规则会改变你的构建方式:
- 所有人都必须使用硬件密钥。“自2023年6月1日起生效”,订户的私钥必须在至少通过 FIPS 140-2 Level 2 或 Common Criteria EAL 4+ 认证的硬件密码模块中生成、存储和使用(§6.2.7.4.1)。允许的方案有:你自己运营的硬件模块;HSM 达到这一标准、且订阅“配置为记录所有访问、操作和配置变更”的云密钥服务;或者签名服务。CA 必须核实这一点,例如寄送一个已预先生成密钥的硬件令牌,或者检查密钥证明(§6.2.7.4.2)。2023年之前,这项要求只适用于 EV 证书。
- 密钥长度。RSA 密钥至少 3072 位,或者使用 P-256、P-384 或 P-521 上的 ECDSA(§6.1.5.2)。
- 有效期。2026年3月1日及之后签发的证书最长 460 天;此前为 39 个月(§6.3.2)。
- 时间戳机构。TSA 证书最长有效 135 个月,其密钥可在
notBefore之后的 15 个月内使用;SHA-1 时间戳令牌自2022年4月起已被禁止。
Microsoft 在此之上还加了自己的要求。自 Windows 10 版本 1607 起,内核模式驱动程序必须由 Microsoft 通过 Windows 硬件开发人员中心签名,因此仅凭商业证书无法加载驱动程序(驱动程序签名策略)。EV 也不再意味着“没有 SmartScreen 警告”:Microsoft 的分发功能状态页面写道,自2024年受信任根证书计划(Trusted Root Program)变更以来,“任何证书类型(OV 或 EV)都无法立即绕过”;信誉按文件哈希和签名身份分别累积。
Microsoft 的托管服务 Artifact Signing(由 Trusted Signing 更名而来)把密钥保存在 Microsoft 运营的 HSM 中,签发的证书“每天续期,有效期只有 72 小时”(证书管理)。只有摘要会离开你的构建机器。证书有效期这么短,不带时间戳的签名三天后就没有用了。同一页面还提到两个细节:由于证书每天都在变化,每个证书配置文件都会获得一个位于 1.3.6.1.4.1.311.97. 之下的持久自定义 EKU,可以把信任锚定在它上面(例如在应用程序控制策略中);该服务的 CA 正在从 CCADB 中移除,也已不在 Microsoft 受信任根证书计划中,但“仍保留在 Windows 平台信任列表中”,所以 Windows 会继续信任它们,其他平台则未必。
用 osslsigncode 签名和验证
osslsigncode 在 Linux 和 macOS 上实现了 Authenticode。实验(debian:forky-20261005-slim 中的 Debian 软件包 2.14)创建了一套私有测试层级:一个根 CA、一个签发 CA,以及一张带 codeSigning EKU 的 RSA 3072 代码签名证书;然后对程序签名,并向 DigiCert 的公共 TSA 申请时间戳:
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年11月)。没有时间戳时,验证使用当前时间,证书过期当天验证就会失败,与任何 TLS 客户端遇到的 certificate has expired 错误相同。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年重新发布的公告中表示“不打算将更严格的验证行为作为默认设置强制执行”。CISA 于2022年1月将该 CVE 加入其已知被利用漏洞目录。给开发者的教训:切勿从自己可执行文件的证书表区域读取数据,并在你管理的机器上设置 EnableCertPaddingCheck。
在 Windows 上:signtool
Windows SDK 中的 SignTool 是参考实现。当前版本要求显式指定摘要算法:“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。 - 验证策略用错。不加
/pa的signtool verify会把有效的商业签名报告为不受信任。 - 证书缺少
codeSigningEKU,或者拿 TLS 证书凑合充当代码签名证书。验证方会拒绝它,就像 OpenSSL 会报 unsuitable certificate purpose 一样。 - 缺少中间证书。在签名中包含完整的证书链(
-certs chain.pem,或在签名主机上安装 CA 的中间证书),否则验证能否通过取决于用户机器上恰好有哪些证书。 - 指望 EV 让 SmartScreen 不再警告。现在信誉需要随时间积累;每个版本都用同一个身份签名,信誉才能延续下去。
- 从自己已签名文件的末尾读取数据(安装程序、自解压程序):这一区域不在签名覆盖范围内。
参考资料
- CA/Browser Forum《代码签名基线要求》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