证书
证书透明度(CT)详解:日志、SCT 与证书监控
证书透明度(Certificate Transparency,CT)要求 CA 把每一张公网可信 TLS 证书提交到只能追加的公开日志,否则浏览器不接受这张证书。日志会返回一个带签名的承诺,叫签名证书时间戳(SCT),浏览器在握手时检查它;任何人都可以在日志里搜索自己域名被签发过哪些证书。截至 2026 年 11 月,Chrome、Safari 和 Firefox 都对公网证书强制执行 CT。
为什么需要证书透明度
浏览器信任的 CA 有很多家,任何一家都能给任何域名签发证书,而在 CT 出现之前,这种签发未必有人知道。2011 年,攻击者入侵荷兰 CA DigiNotar,签发了数百张伪造证书,其中一张 *.google.com 被用来在伊朗监听用户。同一年,还有攻击者通过 Comodo 一个被盗用的代理商账户拿到了多个大型网站的证书。两起事件里,伪造证书都是很晚才被偶然发现的。
Google 的工程师设计 CT,就是要把“偶然发现”变成“必然发现”。协议于 2013 年 6 月发布为 RFC 6962,很快证明了价值:2015 年,CT 日志暴露出 Symantec 未经授权为 Google 域名签发了证书,这是此后一系列事件中的第一起,最终导致各大浏览器不再信任 Symantec 的根证书。
CT 并不能阻止 CA 签发错误的证书,它保证的是这张证书一定会公开,让域名所有者、浏览器厂商和研究者看得到、来得及处理。
CT 日志如何工作
CT 日志是一份公开的、只能追加的证书列表,按 Merkle 树组织,任何人都能低成本地验证条目只增不删。每张证书被哈希成一个叶子,两两哈希向上合并,最顶端的那个哈希值就“锁定”了整个日志的内容。日志对这个值签名,称为签名树头(STH)。
根哈希(签名后即 STH)
/ \
h(0x01 | A | B) h(0x01 | C | D)
/ \ / \
A=h(0x00|e0) B=h(0x00|e1) C=h(0x00|e2) D=h(0x00|e3)
证书 0 证书 1 证书 2 证书 3
两种证明让日志无法作弊:
- 包含证明:证明某张证书在树里。只需要约 log2(n) 个哈希值,一个有十亿条记录的日志大约 30 个哈希就够了。
- 一致性证明:证明新的树头包含了旧树头的全部内容,也就是日志没有改写历史。
日志承诺在最大合并延迟(MMD)内把提交的证书并入树中。监控者下载日志,查找自己关心的证书;审计者检查各种证明。
分片日志与 static-ct-api
日志运营方正在从 RFC 6962 原来的 HTTP 接口迁移到 static-ct-api:日志以静态、可缓存的 Merkle 树“瓦片”(tile)形式发布,运营成本更低,也更容易镜像。截至 2026 年 11 月,Chrome 的 CT 日志政策要求新申请的日志实现 static-ct-api v1.1.0,2027 年 1 月 1 日之前申请的日志仍可选择 RFC 6962,并把 static-ct-api 日志的 MMD 上限定为 1 分钟。Let's Encrypt 目前只运营基于自家 Sunlight 软件的 static-ct-api 日志(Sycamore 和 Willow),原来的 RFC 6962 日志已经退役。
现在的日志还按时间分片:每个分片(比如以 2026 年下半年命名的那个)只接收到期时间落在对应区间内的证书。分片里的证书全部过期后,这个分片就可以冻结并最终下线。
截至 2026 年 11 月,Chrome 的日志列表里有十余家运营方,包括 Google、Cloudflare、DigiCert、Sectigo、Let's Encrypt,以及国内的 TrustAsia(亚洲诚信)。
预证书与 SCT
CA 在签发正式证书之前先把一张“预证书”提交给日志,这样才能把日志的承诺嵌进正式证书。预证书的内容与正式证书完全一致,只多了一个关键的“毒药”扩展(OID 1.3.6.1.4.1.11129.2.4.3),让它无法用于 TLS。
CA CT 日志(至少 2 家运营方)
| 1. 提交预证书(含毒药扩展) --------------->|
| 2. 每个日志返回 SCT <-----------------------| “我会在 MMD 内记录它”
| 3. 签发正式证书,写入 SCT 列表扩展
| (1.3.6.1.4.1.11129.2.4.2)
v
Web 服务器 --- TLS 握手:证书 + 内嵌 SCT ---> 浏览器
校验 SCT 签名和 CT 政策
SCT 里有日志 ID、时间戳和日志对证书数据的签名。它是承诺,不是包含证明:日志保证在 MMD 内记录这条数据,整个生态再去检查它是否真的做到了。CA/B 论坛把已记录的预证书视为 CA 打算签发对应证书的证据,所以预证书签错了同样算错误签发。
用 OpenSSL 就能看到内嵌的 SCT。下面是 2026 年 11 月 letsencrypt.org 网站证书的输出(签名部分已省略):
echo | openssl s_client -connect letsencrypt.org:443 -servername letsencrypt.org 2>/dev/null \
| openssl x509 -noout -issuer -dates -ext ct_precert_scts
issuer=C=US, O=Let's Encrypt, CN=YE2
notBefore=Nov 3 13:44:34 2026 GMT
notAfter=Feb 1 13:44:33 2027 GMT
CT Precertificate SCTs:
Signed Certificate Timestamp:
Version : v1 (0x0)
Log ID : 4C:63:DC:98:E5:9C:1D:AB:88:F6:1E:8A:3D:DE:AE:8F:
AB:44:A3:37:7B:5F:9B:94:C3:FB:A1:9C:FC:C1:BE:26
Timestamp : Nov 3 14:43:04.256 2026 GMT
Signature : ecdsa-with-SHA256 ...
Signed Certificate Timestamp:
Version : v1 (0x0)
Log ID : 46:A2:39:67:C6:0D:B6:46:87:C6:6F:3D:F9:99:94:76:
93:A6:A6:11:20:84:57:D5:55:E7:E3:D0:A1:D9:B6:46
Timestamp : Nov 3 14:43:06.252 2026 GMT
Signature : ecdsa-with-SHA256 ...
这张证书有效期 90 天,按 Chrome 政策需要来自两个不同日志、两个不同运营方的 SCT,它带的正好是两个。
SCT 的三种交付方式
SCT 有三种途径到达浏览器,但实际上几乎所有证书都用第一种。
| 方式 | 原理 | 服务器要改什么 | 截至 2026 年 11 月的状况 |
|---|---|---|---|
| 内嵌在证书里 | CA 把 SCT 列表扩展写进正式证书 | 不用改 | 主流方式 |
| TLS 扩展 | 服务器在 signed_certificate_timestamp 扩展里发送 SCT |
服务器要自己获取并配置 SCT | 支持,但很少用 |
| OCSP 装订响应 | SCT 放在 OCSP 响应的扩展里 | 需要开启 OCSP 装订 | Chrome 只在 148 之前的版本接受;包括 Let's Encrypt 在内的多家 CA 已不再提供 OCSP |
各浏览器的要求
三大浏览器内核现在都会拒绝 SCT 不足的公网证书,具体规则各不相同。下表数字取自截至 2026 年 11 月的官方政策:
| 浏览器 | 对内嵌 SCT 的要求 | 何时开始强制 |
|---|---|---|
| Chrome(及基于 Chromium 的浏览器) | 有效期不超过 180 天需来自 2 个不同日志的 SCT,更长需 3 个;至少一个日志当前仍处于 Qualified、Usable 或 ReadOnly 状态;至少来自两个不同的日志运营方 | 2018 年 4 月 30 日之后签发的证书 |
| Apple 平台(Safari 及使用系统 TLS 的 App) | 180 天以内 2 个,181~398 天 3 个,同一运营方计入的数量有上限;至少一个 SCT 来自 RFC 6962 日志 | 2018 年下半年起 |
| Firefox | Mozilla 根证书计划内 CA 签发的所有证书都必须记录到 CT 日志 | 桌面版 135、安卓版 145 起 |
通过 TLS 扩展发送 SCT 时,Chrome 要求至少两个来自当前受认可日志、分属两个不同运营方的 SCT。另外,如果 Chrome 本地的日志列表超过 70 天没有更新,它会暂停执行 CT,避免长期不更新的浏览器在日志变动时大面积报错。
基于 Chromium 的国产浏览器(360、QQ 浏览器等)是否、以及从哪个版本开始执行 CT,取决于各自跟进的 Chromium 版本和自身改动,排查问题时以实测为准。
有效期缩短也会影响这些规则。2026 年 3 月 15 日起证书最长 200 天,所以有效期 181~200 天的证书在 Chrome 里仍需要 3 个 SCT;2027 年 3 月 15 日上限降到 100 天以后,所有新签发的公网证书都只落在“2 个 SCT”这一档。见 47 天证书。
证书不满足政策时,Chrome 会显示 NET::ERR_CERTIFICATE_TRANSPARENCY_REQUIRED。链到用户或企业自行安装的根证书的证书不要求 CT,所以 私有 CA 签发的内网证书不用进日志。
监控自己域名的证书
CT 只有在有人看的时候才起作用,最省事的监控就是定期在日志里搜索自己的域名。crt.sh 这类基于 CT 数据的搜索服务会按名称索引所有已记录的证书,用 % 作通配符可以把子域名也查出来:
curl -s "https://crt.sh/?q=%25.example.com&output=json" \
| jq -r '.[] | [.not_before, .issuer_name, .name_value] | @tsv' | sort -r | head
本站的 CT 证书搜索 在浏览器里做同样的查询。查看结果时重点关注:
- 你没用过的 CA。 如果某张证书来自你从未下过单的 CA,要查清原因。CAA 记录可以事先限定哪些 CA 能为你的域名签发,CT 则告诉你这个限制有没有被遵守。注意在阿里云、腾讯云等平台购买的证书实际由合作的 CA 签发,颁发者一栏显示的是 CA 的名称而不是云厂商,看到陌生名字先对照自己的订单。
- 不认识的主机名。 可能是别的部门自行搭建的系统、被遗忘的测试环境,最坏的情况是有人控制了你的某个子域名。
- 数量异常。 证书突然大量出现,可能是自动化脚本陷入循环,也可能是 ACME 账户被盗用。
发现未经授权的证书,联系签发它的 CA 要求吊销;按照 Baseline Requirements,CA 必须调查处理。
隐私问题:CT 会公开主机名
公网证书 SAN 列表里的每个名称都会被公开,通常在签发后几分钟内。vpn.example.com、jenkins.example.com、test-newclient.example.com 这样的名字会暴露内部架构,有时还会泄露业务计划。自动化扫描器也在盯着 CT 日志,新主机名一出现就会被探测。对于刚装好、还没完成初始化设置的网站程序,这是实实在在的风险。
能减少暴露的办法不多:
- 用通配符证书(
*.example.com),日志里只出现通配符。代价是一把私钥保护很多台主机,见 通配符证书。 - 纯内部系统用私有 CA,链到本地信任根的证书不会进日志。
- 不要把秘密写进主机名。 写进公网证书的名字,就当作已经公开。
在 CT 第二版标准化期间,曾有人提议在日志中隐去部分域名,但浏览器从未采用。
RFC 6962 与 RFC 9162
RFC 9162 于 2021 年 12 月发布,定义了 CT 2.0,并在形式上取代了 RFC 6962,但实际部署的生态仍然使用第一版的数据结构。浏览器校验 v1 SCT,CA 内嵌 v1 SCT 列表,static-ct-api 改的是日志的发布方式,数据格式仍沿用 v1。Apple 甚至要求至少一个 SCT 来自 RFC 6962 日志。实践中说“CT”,指的就是 RFC 6962 的数据,通过原接口或 static-ct-api 提供。
CT 做不到什么
CT 是一套发现机制,边界很清楚:它不能阻止错误签发,不负责吊销,而且依赖有人真的去监控日志。它也不说明网站是否可信:钓鱼网站的证书和银行的证书一样会被记录。把它和限制签发的 CAA 记录、以及处理问题证书的吊销机制(见 OCSP 与 CRL)配合使用,才构成完整的防线。
常见问题
服务器需要为证书透明度做什么配置吗?
几乎不需要。公网 CA 会把 SCT 直接嵌进证书,任何服务器只要部署这张证书就满足 CT 要求。通过 TLS 扩展发送 SCT 是可选的,很少有人用。
能不能让某个子域名不出现在 CT 日志里?
只要它写在公网可信证书里就不能。可以改用通配符证书,这样日志里只出现 *.example.com;纯内部系统可以用私有 CA,因为链到本地安装根证书的证书不要求 CT。
NET::ERR_CERTIFICATE_TRANSPARENCY_REQUIRED 是什么意思?
Chrome 收到了一张公网可信证书,但随附的有效 SCT 不足以满足它的 CT 政策。通常是 CA 的问题(比如 SCT 来自 Chrome 已不再接受的日志),或者是企业内网的 HTTPS 检测设备造成的;重新签发证书一般就能解决。
CT 和吊销检查是一回事吗?
不是。CT 让签发行为公开可见,便于发现问题;它不告诉浏览器某张证书是否已被吊销。吊销靠的是 CRL、OCSP,或 CRLSets、CRLite 这类浏览器机制。