证书透明度(CT)日志:Merkle 树审计架构与 RFC 6962 协议

协议详解 · 2026-06-02

背景:CA 体系的信任危机

公钥基础设施(PKI)是互联网安全的基石,但传统 PKI 体系存在一个根本性的监管漏洞:任何人都可以查看已签发的证书,但没有任何机制能让域名所有者或第三方及时发现未经授权签发的证书。

历史上多次 CA 安全事件暴露了这一缺陷:

  • 2011 年 DigiNotar 事件:黑客入侵荷兰 CA DigiNotar,签发了包括 *.google.com 在内的数百个伪造证书,用于对伊朗用户进行中间人攻击。事件直到数周后才被发现,最终导致 DigiNotar 破产。
  • 2012 年 Trustwave 事件:Trustwave 为企业客户签发了一个 subordinate CA 证书,该企业可以借此签发任意域名的证书而不被监控。
  • 2015 年 CNNIC 事件:中国 CNNIC 的 subordinate CA 被发现签发了 Google 域名的伪造证书。
  • 2016 年 WoSign/StartCom 事件:中国 CA WoSign 被发现倒签证书日期、签发未经验证的证书。
这些事件的共同问题是:没有人在看。即使 CA 签发了不应该签发的证书,也没有人能够及时发现。

CT 的核心思想

证书透明度(Certificate Transparency,CT)由 Google 工程师 Ben Laurie 等人于 2012 年提出,其核心思想极其简洁:

所有公开信任的证书都应该被记录在公开、可审计、仅追加(append-only)的日志中。
CT 不是要替代现有的 CA 体系,而是在其之上增加一层透明性保障。通过 CT:

  • 域名所有者可以监控是否有未经授权为自己的域名签发的证书
  • CA 和浏览器可以检测其他 CA 的异常签发行为
  • 研究人员可以分析整个 CA 体系的健康状况
  • 任何人都可以审计日志的完整性和正确性

CT 系统架构

CT 系统由三个核心组件组成:

日志服务器(CT Log)

日志服务器是 CT 系统的核心,负责接收证书提交、维护 Merkle 哈希树、提供审计证明。

日志服务器的关键属性

  • 仅追加:一旦证书被记录,不能被删除或修改
  • 公开可验证:任何人都可以获取日志的完整副本并验证其一致性
  • 高可用性:日志服务应保持高可用性,确保证书提交不被拒绝
日志服务器的运营
  • 任何组织都可以运营 CT 日志服务器
  • 浏览器厂商(Chrome、Safari、Firefox)维护自己的日志服务器列表
  • 日志服务器需要经过浏览器厂商的审计和认可,其根证书才会被纳入信任列表

监控器(Monitor)

监控器是 CT 生态中的"守望者",持续跟踪一个或多个 CT 日志服务器的新增条目。

监控器的职责:

  • 定期从日志服务器获取新的证书条目
  • 验证日志的一致性证明(Consistency Proof)
  • 根据预定义的规则检测可疑证书
  • 向域名所有者或安全团队发送告警
运营的监控服务包括:
  • crt.sh:由 Comodo 运营的公共 CT 查询服务
  • Facebook CT Monitor:Facebook 运营的内部监控系统
  • Certificate Transparency Google Groups:Google 运营的监控服务

审计器(Auditor)

审计器是 CT 生态中的"审计师",负责验证日志服务器的行为是否符合协议规范。

审计器的职责:

  • 验证 Merkle 一致性证明:确保日志的追加操作是合法的
  • 验证 SCT(签名证书时间戳)签名的有效性
  • 检测日志分叉(split-view attack):同一日志服务器对不同客户端展示不同的视图

Merkle 哈希树

Merkle 哈希树(Merkle Hash Tree)是 CT 系统的数学基础,由 Ralph Merkle 在 1979 年提出。

基本结构

Merkle 哈希树是一棵二叉树,其中:

  • 每个叶节点是数据块的哈希值
  • 每个内部节点是其两个子节点拼接后的哈希值
  • 根节点(Root Hash)是整个数据集的"指纹"
对于 CT 日志,每个叶节点对应一个提交的证书(或预证书)。

$$H_{\text{leaf}} = \text{SHA-256}(0x00 \| \text{cert\_data})$$

$$H_{\text{internal}} = \text{SHA-256}(0x01 \| H_{\text{left}} \| H_{\text{right}})$$

其中 $0x00$ 和 $0x01$ 是域分隔符(domain separator),防止叶节点和内部节点之间的碰撞攻击。

Merkle 包含证明(Inclusion Proof)

Merkle 包含证明用于证明某个特定数据块(证书)包含在日志中。

验证过程

  • 给定叶节点的索引 $i$ 和树的当前大小 $n$
  • 日志服务器返回从叶节点到根路径上的所有"兄弟节点"哈希值
  • 验证者从叶节点开始,逐层向上计算,最终得到根哈希
  • 将计算得到的根哈希与已知的 Signed Tree Head(STH)中的根哈希进行比较
证明大小:对于包含 $n$ 个条目的树,包含证明的大小为 $O(\log_2 n)$。对于包含 100 万个条目的日志,证明大小约为 20 个哈希值(640 字节)。

Merkle 一致性证明(Consistency Proof)

Merkle 一致性证明用于证明日志的某个历史版本是当前版本的合法前缀——即没有删除、修改或插入操作。

验证过程

  • 给定两个树大小 $m$ 和 $n$($m < n$)
  • 日志服务器返回一组节点,证明大小为 $m$ 的树的根是大小为 $n$ 的树的子树
  • 验证者检查这些节点是否同时满足两棵树的 Merkle 哈希关系
证明大小:一致性证明的大小为 $O(\log_2 n)$。

Signed Tree Head(STH)

Signed Tree Head 是日志服务器对当前树状态的密码学承诺,包含:

  • tree_size:当前树中的叶节点数量
  • timestamp:STH 生成的时间戳(毫秒精度)
  • sha256_root_hash:Merkle 树的 SHA-256 根哈希
  • tree_head_signature:日志服务器对以上数据的数字签名
STH 使用日志服务器的私钥签名,签名算法为 SHA-256 with RSA/ECDSA(CT v2 支持 Ed25519)。

SCT:签名证书时间戳

SCT(Signed Certificate Timestamp)是日志服务器对"已接收证书提交"的密码性承诺。浏览器要求证书必须附带有效的 SCT 才能被信任。

SCT 的结构

CODE
struct {
    Version sct_version;          // CT 版本(v1 = 0)
    LogID log_id;                 // 日志服务器的唯一标识(SHA-256 公钥哈希)
    uint64 timestamp;             // 毫秒级 Unix 时间戳
    CtExtensions extensions;      // CT 扩展(通常为空)
    digitally-signed signature;   // 数字签名
} SignedCertificateTimestamp;

SCT 的三种交付方式

RFC 6962 定义了三种 SCT 交付方式:

1. X.509v3 扩展(Embedded SCT)

SCT 列表直接嵌入到 X.509 证书的扩展字段中:

ASN1
1.3.6.1.4.1.11129.2.4.2 ::= SEQUENCE OF SignedCertificateTimestamp

优点:客户端无需额外连接即可验证 SCT 缺点:需要 CA 在签发证书前从日志服务器获取 SCT

2. TLS 扩展(SCT Extension)

在 TLS 握手期间,服务器通过 signed_certificate_timestamp 扩展发送 SCT 列表。

优点:支持为已有证书动态附加新的 SCT 缺点:需要 TLS 服务器配置支持

3. OCSP Stapling(OCAP Response)

服务器通过 OCSP stapling 机制附带 SCT 列表。

优点:不需要修改证书或 TLS 服务器配置 缺点:依赖 OCSP 基础设施

Chrome 的 CT 要求

Chrome 浏览器要求证书必须满足以下 CT 要求之一:

  • 至少 1 个来自 Google 运营的日志的 SCT + 至少 1 个来自非 Google 运营的日志的 SCT
  • 或者根据证书的有效期,需要更多 SCT(有效期越长,要求的 SCT 数量越多)

CT 协议流程

提交证书

获取 STH

CODE
Client                                          CT Log
  |                                                |
  |--- GET /ct/v1/get-sth -----------------------|
  |                                                |
  |<-- STH ----------------------------------------|
  |    {                                           |
  |      "tree_size": 5000000,                    |
  |      "timestamp": 1620000000000,              |
  |      "sha256_root_hash": "base64-hash",       |
  |      "tree_head_signature": "base64-sig"      |
  |    }                                           |

获取包含证明

CODE
Client                                          CT Log
  |                                                |
  |--- GET /ct/v1/get-proof-by-hash ------------>|
  |    ?hash=base64-encoded-leaf-hash             |
  |    &tree_size=5000000                          |
  |                                                |
  |<-- Proof --------------------------------------|
  |    {                                           |
  |      "leaf_index": 1234567,                   |
  |      "audit_path": ["base64-hash-1", ...]     |
  |    }                                           |

获取一致性证明

CODE
Client                                          CT Log
  |                                                |
  |--- GET /ct/v1/get-sth-consistency ---------->|
  |    &first=1000000                              |
  |    &second=5000000                             |
  |                                                |
  |<-- Consistency Proof --------------------------|
  |    {                                           |
  |      "consistency": ["base64-hash-1", ...]    |
  |    }                                           |

CT v2(RFC 9162)

CT v2(RFC 9162,2021 年发布)对 CT v1 进行了重要改进:

主要变化

  • 支持 Ed25519 签名:除了 RSA 和 ECDSA,CT v2 支持 Ed25519 签名算法,提供更快的签名验证速度
  • 证书格式支持:支持更大的证书链和更长的证书
  • 预证书(Precertificate)处理:改进了预证书的提交和嵌入机制
  • 日志操作符更换:支持日志服务器更换签名密钥

预证书机制

预证书(Precertificate)是 CT 引入的特殊证书格式,用于在 CA 签发最终证书之前将其提交到 CT 日志。

预证书包含一个特殊的 poison 扩展(OID: 1.3.6.1.4.1.11129.2.4.3),使浏览器拒绝将其作为有效证书使用。这样,CA 可以先将预证书提交到 CT 日志获取 SCT,然后将 SCT 嵌入到最终签发的证书中。

CT 的安全分析

Split-View 攻击

最严重的威胁是分叉视图攻击(Split-View Attack):日志服务器对不同客户端展示不同的树状态。

攻击场景

  • 攻击者控制了一个 CA,为 bank.com 签发了伪造证书
  • 攻击者向日志服务器提交了真实证书,获取了有效的 SCT
  • 日志服务器对攻击者展示包含该证书的树状态,对监控器展示不包含该证书的树状态
  • 监控器无法发现伪造证书
防御措施
  • 多个独立的审计器持续验证日志一致性
  • 监控器交叉验证多个日志服务器的状态
  • 浏览器要求来自多个独立日志的 SCT

前置提交攻击(Front-Running Attack)

攻击者在证书提交到日志之前窃取其内容(例如通过监控提交接口),然后抢先提交。

防御措施

  • 使用 HTTPS 保护提交通道
  • 预证书的 poison 扩展使窃取的预证书无法直接使用

CT 在国密 PKI 体系中的适用性

国密 PKI 体系(基于 SM2/SM4/SM3 算法)同样面临 CA 签发行为缺乏透明性的问题。将 CT 引入国密体系需要考虑以下因素:

算法替换

CT 协议中的密码学算法可以替换为国密算法:

  • 哈希函数:SHA-256 → SM3
  • 签名算法:RSA/ECDSA → SM2
  • Merkle 树构造:使用 SM3 替代 SHA-256 进行哈希计算

基础设施要求

  • 日志服务器:需要运营国密兼容的 CT 日志服务器
  • 浏览器支持:国密浏览器需要实现 CT 验证逻辑
  • CA 支持:国密 CA 需要在证书签发流程中集成 CT 提交
  • 监控生态:需要建设国密 CT 监控服务

标准参考

  • GM/T 0036-2014:基于 SM2 算法的证书格式规范
  • GM/T 0024-2014:国密 TLS 协议
  • 可以参考 RFC 6962 设计国密兼容的 CT 协议扩展

相关实践