简短回答:证书透明度(CT,RFC 6962)要求 CA 把每一张公开受信任的 TLS 证书提交到公开的、只追加(append-only)的日志中。每个日志都会返回一份带签名的承诺,保证收录该证书,这就是 SCT;CA 再把这些 SCT 嵌入证书。如果公开受信任的证书缺少足够数量、且来自足够多个独立日志的 SCT,Chrome、Safari 和 Firefox 就会拒绝它。这对你意味着两件事。任何人都能通过 Cert Spotter、crt.sh 或你自己的监控程序,看到为你的域名签发的每一张证书。而你也应该监控这些日志,因为一张你没有申请过的证书,是 DNS 被接管、账号被盗用或 CA 行为失当的最早迹象。
CT 的工作原理
CT 日志是一棵默克尔树(Merkle tree),也就是 Git 和比特币所用的那种只追加的哈希结构。任何人都可以下载它,检查其中的条目从未被删除或修改,并获得某张证书已被收录的简短证明。引入 CT 后,证书的签发流程如下:
- CA 构建一张预证书:内容与最终证书相同,另外加上一个标记为关键(critical)的“毒化”扩展(OID
1.3.6.1.4.1.11129.2.4.3),使它无法当作真正的证书使用。 - CA 把预证书提交给多个日志。每个日志返回一个签名证书时间戳(SCT),即日志对该条目的签名和一个时间戳,它承诺在日志的最大合并延迟(MMD)内收录该条目。
- CA 签发最终证书,并把这些 SCT 嵌入一个 X.509 扩展(OID
1.3.6.1.4.1.11129.2.4.2)中。 - 浏览器对照自己的受信任日志列表检查 SCT 签名,并按自己的策略清点这些 SCT 是否足够。
- 监控方(monitor)读取日志,报告其关注的名称下出现的证书。审计方(auditor)检查日志是否始终保持一致、是否兑现了承诺。
大多数情况下,预证书和最终证书都会被记录到日志中,所以搜索工具常常把同一张证书显示两次。
真实证书中的 SCT
openssl x509 -text 可以解码嵌入的 SCT。下面是 letsencrypt.org 在2026年10月20日提供的证书:
subject=CN=letsencrypt.org
issuer=C=US, O=Let's Encrypt, CN=YE2
notBefore=Sep 4 14:34:32 2026 GMT
notAfter=Dec 3 14:34:31 2026 GMT
CT Precertificate SCTs:
Signed Certificate Timestamp:
Version : v1 (0x0)
Log ID : D8:09:55:3B:94:4F:7A:FF:C8:16:19:6F:94:4F:85:AB:
B0:F8:FC:5E:87:55:26:0F:15:D1:2E:72:BB:45:4B:14
Timestamp : Sep 4 15:33:02.377 2026 GMT
Extensions: none
Signature : ecdsa-with-SHA256
30:46:02:21:00:D0:2E:25:A4:9D:7F:C8:18:D6:7B:5E:...
Signed Certificate Timestamp:
Version : v1 (0x0)
Log ID : 46:AF:86:3D:3B:3E:E5:9F:A5:77:DE:A8:24:5D:36:B0:
D9:ED:22:A2:23:F4:61:77:41:22:94:52:EE:95:50:5F
Timestamp : Sep 4 15:33:02.552 2026 GMT
Extensions: 00:00:05:00:24:DF:DB:0C
Signature : ecdsa-with-SHA256
30:44:02:20:31:2F:83:D6:42:65:26:0D:58:D7:DD:C3:...
日志 ID 是日志公钥的 SHA-256 哈希。在 Chrome 的日志列表(log_list.json,2026年10月19日版)中查找这两个 ID,结果如下:
| 日志 ID(base64) | 日志 | 运营方 | 类型 | 状态 |
|---|---|---|---|---|
2AlVO5RPev/IFhlvlE+Fq7D4/F6HVSYPFdEucrtFSxQ= | Xenon2026h2 | RFC 6962,MMD 24 小时 | 可用 | |
Rq+GPTs+5Z+ld96oJF02sNntIqIj9GF3QSKUUu6VUF8= | Tuscolo2026h2 | Geomys | static-ct-api,MMD 60 秒 | 可用 |
两个日志,来自两个不同的运营方,这是 90 天证书的最低要求。第二个 SCT 的扩展并不是无意义的数据。遵循 static-ct-api 的日志会把条目在日志中的位置写进 SCT:扩展类型 0(leaf_index),长度 5,以及一个 40 位的索引,这里是 618,650,380。监控方可以直接获取这个条目,而不必去搜索它。
SCT 解码器可以替你完成这项查找。它会显示每个日志的名称、该日志在 Chrome 和 Apple 列表中的状态,以及这组 SCT 是否满足各浏览器的策略。证书解码器则会把 SCT 扩展与证书的其余部分一起显示出来。
SCT 可以通过三种方式到达浏览器:
- 嵌入证书中,如上例所示。几乎所有 CA 都这样做,你不需要做任何事。
- 放在 TLS 扩展
signed_certificate_timestamp中。服务器发送它自己获取的 SCT。支持这种方式的服务器很少。 - 通过 OCSP Stapling 附在 OCSP 响应中。Chrome 只在“148 版之前的版本中”接受这类 SCT,而随着 Let's Encrypt 等 CA 停用 OCSP,这条途径正在逐渐消失(参见2026年的 OCSP 与 CRL)。
浏览器的要求
三大浏览器引擎都对其公共根证书计划中的 CA 所签发的证书强制执行 CT。
| Chrome | Apple(Safari、iOS、macOS) | Firefox | |
|---|---|---|---|
| 嵌入的 SCT,有效期 ≤ 180 天 | 2 个,来自不同日志 | 2 个 | 2 个,来自不同日志 |
| 嵌入的 SCT,有效期更长 | 3 个,来自不同日志 | 3 个(181~398 天) | 3 个,来自不同日志 |
| 运营方多样性 | 至少 2 个不同的日志运营方 | 每个运营方最多计入 1 个(≤ 180 天)或 2 个(有效期更长)SCT | 至少 2 个不同的日志运营方 |
| 其他条件 | 至少一个嵌入的 SCT 来自当前处于 Qualified(合格)、Usable(可用)或 ReadOnly(只读)状态的日志 | 至少一个嵌入的 SCT 来自当前已获批准的日志;至少一个 SCT 来自 RFC 6962 日志 | 至少一个 SCT 来自当前可接纳的日志;日志列表取自 Chromium |
| 通过 TLS 或 OCSP 传递的 SCT | 2 个,来自当前受信任的日志,涉及 2 个运营方;OCSP 仅限 Chrome 148 之前 | 2 个,来自当前已获批准的日志,其中一个通过 TLS 或 OCSP 传递 | 2 个,来自可接纳的日志,涉及 2 个运营方 |
| 不满足时的错误 | NET::ERR_CERTIFICATE_TRANSPARENCY_REQUIRED | 连接失败 | MOZILLA_PKIX_ERROR_INSUFFICIENT_CERTIFICATE_TRANSPARENCY |
来源:Chrome CT 策略、Apple 的 CT 策略(2025年4月)和 Mozilla 的 CT 页面。根据 MDN,Firefox 从 135 版(2025年2月)起在桌面端强制执行 CT,从 145 版(2025年11月)起在 Android 上强制执行。首选项 security.pki.certificate_transparency.mode 显示当前模式:2 表示强制执行。
由此带来两个实际影响:
- 日志列表过旧时,浏览器会停止强制执行 CT。Chrome 只在其最新日志列表距今不足 70 天时才强制执行 CT,Firefox 的列表则在 10 周后过期。几个月没有更新的机器根本不会强制执行 CT,所以不要用这样的机器测试 CT 失败的情况。
- 私有 CA 不受 CT 要求约束。Chrome 的企业说明指出,CT“仅适用于公开受信任的 CA”。由你自己安装的根证书签发的证书不需要 SCT,而且日志本来也不会接受它们。
日志本身
2026年10月19日的 Chrome 日志列表中有 21 个可用的 RFC 6962 日志,另有 2 个只读日志和 3 个已停用日志。列表中还有 22 个可用和 37 个合格的 static-ct-api 日志。共有十一家运营方运行着可用或合格的日志:Cloudflare、DigiCert、Geomys、GoDaddy、Google、HARICA、IPng Networks、Let's Encrypt、Microsec、Sectigo 和 TrustAsia。日志按时间分片。名为 2026h2 的日志只接受在2026年下半年过期的证书,这样每个日志的大小都有上限,而且在其中的证书全部过期后,该日志就可以停用。
两类日志都通过相同的 RFC 6962 端点(add-chain、add-pre-chain)接收提交。它们的区别在于监控方如何读取。RFC 6962 日志响应动态的 API 调用:
{"tree_size":2980148930,"timestamp":1792499093002,"sha256_root_hash":"wr3F5apMl2E7wQHqbAA1yKsqaZbAzBXTT8WTAeRAy8M=","tree_head_signature":"BAMARzBFAiEAsC3Pr7fsPwy0vchrMDIXka7HxrZANu5tcXclDCUqqSYCIGJ64SaIT0tjM5hCGfycNGPfu6FP8q8RvW14Q4SoyvVU"}
单个半年分片就有接近三十亿个条目。static-ct-api(“瓦片式”,tiled)日志把签名树头(signed tree head)发布为纯文本的检查点(checkpoint),把条目发布为不可变的瓦片(tile),每块 256 个条目,可以由 CDN 缓存:
tuscolo2026h2.sunlight.geomys.org
1007870339
aRhh7dPjYarH0itUM1LD+XJw7CW41z1+9tSnf3AizuY=
— tuscolo2026h2.sunlight.geomys.org W+DWDWi4dqCfFx8da0IS4+jwHKVL6ETEHGQCHn5y8rKflUhLRtLHQ3c6...
— grease.invalid Aui1jDc8Y7Uve7seHd/Pg1p+wHSUmw0+MTNyEKEAAbHaE5bY3Gi/MUZ3zBuk+NxUyhJL...
— tuscolo2026h2.sunlight.geomys.org 7BiSLwAAAaFZRnbMBAMARzBFAiA++3wWdtsK2H8l3IxZh6H/OQml...
前几行依次是日志标识(origin)、树大小和根哈希。以“—”开头的行是签名。注意,监控用的 URL(skylight)与提交用的 URL(sunlight)不同。两者都列在日志列表中。
查找你的证书
有两个公开的搜索服务,几乎覆盖了所有日志。
Cert Spotter API(SSLMate)。它的 issuances 端点会列出某个域名的未过期证书。不使用 API 密钥也能调用,但每小时的查询次数有限。用于生产环境时,SSLMate 要求你注册以获取 API 密钥。下面是 ecdsa.com 的证书:
curl -s "https://api.certspotter.com/v1/issuances?domain=ecdsa.com&include_subdomains=true&expand=dns_names&expand=issuer"
{
"id": "16609031524",
"tbs_sha256": "8c6f1f0bef5434da2012d5f29eab13f00bcec659f17832c07872ff600f61ecf5",
"cert_sha256": "1396447e3454a081fe4e48dc1d5de18b7e3cf857ec1d7ff3c987d091c9024b04",
"dns_names": [
"ecdsa.com"
],
"pubkey_sha256": "9a1fa381dad4630b83cf3a1a1a7a3da7f04577db8a41933dd9bac49dabfa8c29",
"issuer": {
"friendly_name": "Let's Encrypt",
"pubkey_sha256": "2e8307068b6db620e4a39d068b5dee5d6ef5788cbb2c0b6d23ead84fcc17178c",
"name": "C=US, O=Let's Encrypt, CN=YR1"
},
"not_before": "2026-08-19T14:52:58Z",
"not_after": "2026-11-17T14:52:57Z",
"revoked": false
}
汇总起来,响应中共有五条签发记录:
16609031524 2026-08-19 2026-11-17 Let's Encrypt ecdsa.com
16609045002 2026-08-19 2026-11-17 Let's Encrypt ecdsa.com,www.ecdsa.com
17525925247 2026-10-01 2026-12-30 Let's Encrypt *.ecdsa.com,ecdsa.com
17875915585 2026-10-19 2027-01-17 Let's Encrypt ecdsa.com,www.ecdsa.com
17878726033 2026-10-20 2027-01-18 Let's Encrypt ecdsa.com
结果按 SSLMate 发现它们的顺序返回。要翻页,把最后一个 id 作为 after= 传入。返回空数组时会附带 Retry-After 头部,因此这个端点可以用作轮询数据源。留意 pubkey_sha256 字段的用途。把它与你服务器上的密钥对比,就能知道哪些证书实际部署了,哪些签发后从未使用。
crt.sh(Sectigo)提供网页界面和 JSON 输出(https://crt.sh/?q=example.com&output=json)。它是免费的,覆盖的历史非常长,包括已过期的证书。它也是一个繁忙的共享服务。我们在2026年10月20日发起的四次 JSON 查询全部返回了 502 Bad Gateway。把它当作一种便利,而不要让你的监控依赖它。
CT 历史查询工具按时间顺序显示某个域名的证书,以及每张证书的颁发者、有效期和名称。
监控你的域名
搜索回答的是“有哪些证书?”。监控回答的是“有什么新证书?”,并在证书签发后几分钟或几小时内向你发出告警。可选方案:
- 托管的监控服务。SSLMate 托管的 Cert Spotter 以及多家 CA 的控制台,会在你的域名出现新证书时发送电子邮件或 webhook 通知。
- 开源的
certspotter(v0.25.0,2026年9月)自己读取日志。你在~/.certspotter/watchlist中列出名称(开头加一个点即可覆盖所有子域名,例如.example.com),每出现一个匹配项,它就给你发邮件,或者运行~/.certspotter/hooks.d中的脚本。从 v0.24 起,它还提供certspotter-authorize,用来标记预期中的证书,这样只有意料之外的证书才会触发告警。 - 把搜索 API 当作数据源。用
after=轮询 Cert Spotter 的 issuances 端点,把每张新证书与你的自动化流程签发的证书逐一比对。
哪些情况需要告警:
- 你没有使用的颁发者。与你的 CAA 记录对照。如果某张证书来自你的 CAA 禁止的 CA,这就是一起 CA 事故,必须报告给该 CA。
- 你不认识的名称,尤其是被遗忘的子域名。一张签发给
old-campaign.example.com的证书,可能意味着某条悬空的 DNS 记录已被他人接管。 - 不是你生成的公钥,出现在由你的自动化流程管理的名称上。
- 通配符证书,前提是你的策略中不使用它们。
随着 47 天证书的到来,合法的签发会变得频繁。如果没有 certspotter-authorize,也不与 ACME 客户端的记录做比对,告警疲劳会让你忽略掉那张真正要紧的证书。
CT 与私有名称
公开受信任证书中的一切都会被公开:主题备用名称(SAN)列表中的每一个名称,而且永久公开。像 jenkins.corp.example.com 或 staging-payments.example.com 这样的内部主机名,一旦你为它们申请了公开证书,就会立即公开;任何读取日志的人都能看到它们,包括那些四处扫描、寻找攻击目标的人。以下做法来自 Chrome 的企业说明和常见实践:
- 为内部名称使用私有 CA。CT 不适用,也不会公开任何内容。
- 必须使用公开证书时,改用通配符证书(
*.corp.example.com)。它会暴露所在的 DNS 区域,但不会暴露每一台主机。 - 不要指望让公开 CA 不把证书记入日志。那样的话,这张证书在每个强制执行 CT 的浏览器中都会验证失败。
常见错误
- 以为 SCT 能证明证书已被收录。SCT 是在 MMD 内收录该条目的承诺。审计方和监控方负责检查日志是否兑现了这个承诺。
- 重复计数。在大多数搜索工具中,同一次签发的预证书条目和证书条目会显示为两行。它们的颁发者和序列号相同,所以统计证书数量之前,先按这两项的组合去重。
- 指望从 CT 中获得吊销信息。被吊销的证书会永远留在日志中。吊销是另一套独立的机制。
- 用旧浏览器测试强制执行。浏览器的日志列表过时后,CT 强制执行会自动关闭。
- 只依靠一个免费服务做监控。crt.sh 会出现服务中断。再增加一个数据源,或者运行你自己的监控程序。
参考资料
- RFC 6962:Certificate Transparency(预证书、SCT、日志 API)。RFC 9162 定义了 CT 2.0,但浏览器并未采用。
- static-ct-api:瓦片式日志的读取 API,以及
leaf_index扩展。 - Chrome CT 策略、Chrome 日志列表、面向企业的 CT。
- Apple 的证书透明度策略及其日志列表。
- MDN:证书透明度、Mozilla 的证书透明度页面,以及 Firefox 135 的版本说明。
- SSLMate:Cert Spotter API,以及 GitHub 上的 certspotter。
文中的命令输出由 node:24.22.0-trixie 中的 OpenSSL 3.5.7 和 curl 8.14.1 于2026年10月20日生成。