3. Log Format and Operation (日志格式与运行)
3. Log Format and Operation (日志格式与运行)
任何人都可以向证书日志提交证书以供公开审计; 然而, 由于 TLS 客户端不会接受未记录的证书, 预期证书持有者或其 CA 通常会提交它们. 日志是此类证书的单一, 持续增长, 仅追加的 Merkle 树.
当有效证书被提交到日志时, 日志必须立即返回签名证书时间戳 (Signed Certificate Timestamp, SCT). SCT 是日志的承诺, 保证在称为最大合并延迟 (Maximum Merge Delay, MMD) 的固定时间内将该证书纳入 Merkle 树. 若日志先前已见过该证书, 它可以返回与之前相同的 SCT. TLS 服务器必须将一个或多个日志的 SCT 与证书一起呈现给 TLS 客户端. TLS 客户端必须拒绝没有针对终端实体证书的有效 SCT 的证书.
各日志定期将其所有新条目追加到 Merkle 树并对树根签名. 审计者因此可以验证每个已签发 SCT 的证书确实出现在日志中. 日志必须在签发 SCT 后的最大合并延迟期限内将证书纳入其 Merkle 树.
日志运营者不得对从日志检索或共享数据施加任何条件.
3.1. Log Entries (日志条目)
任何人都可以向任意日志提交证书. 为使每条已记录证书可归因于其签发者, 日志必须公布可接受根证书列表 (该列表可以有益地为主要浏览器厂商所信任的根证书之并集). 每个提交的证书必须附带所有附加证书, 以将证书链验证至可接受的根证书. 提交给日志服务器的链本身可以省略根证书.
或者, (根及中间) 证书颁发机构可以在签发之前向日志提交证书. 为此, CA 提交预证书 (Precertificate), 日志可用其创建对与将签发证书有效的条目. 预证书通过在待签发证书的终端实体 TBSCertificate 末尾添加特殊的 critical 毒化扩展 (OID 1.3.6.1.4.1.11129.2.4.3, 其 extnValue OCTET STRING 含 ASN.1 NULL 数据 (0x05 0x00)) 构造 (该扩展确保预证书无法被标准 X.509v3 客户端验证), 并使用以下之一对所得 TBSCertificate [RFC5280] 签名:
-
专用 (CA:true, 扩展密钥用法 Extended Key Usage: Certificate Transparency, OID 1.3.6.1.4.1.11129.2.4.4) 预证书签名证书 (Precertificate Signing Certificate). 预证书签名证书必须由最终将签署终端实体 TBSCertificate 以产生终端实体证书的 (根或中间) CA 证书直接认证 (注意日志可以放宽标准验证规则以允许这一点, 只要签发的证书有效),
-
或者, 将签署最终证书的 CA 证书.
如上, 若使用预证书签名证书, 预证书提交必须附带预证书签名证书以及验证至可接受根证书所需的全部附加证书. TBSCertificate 上的签名表明 CA 签发证书的意图. 该意图被视为具有约束力 (即, 预证书的错误签发视为与最终证书的错误签发等同). 每个日志验证预证书签名链并对相应 TBSCertificate 签发签名证书时间戳.
日志必须验证提交的终端实体证书或预证书具有使用提交者提供的中间 CA 证书链回到可信根 CA 证书的有效签名链. 日志可以接受已过期, 尚未生效, 已撤销或根据 X.509 验证规则并非完全有效的证书, 以适应 CA 证书签发软件的怪异行为. 但是, 日志必须拒绝发布没有到已知根 CA 有效链的证书. 若证书被接受并已签发 SCT, 接受日志必须存储用于验证的整条链, 包括证书或预证书本身以及用于验证链的根证书 (即使提交时省略了根), 并必须在请求时提供该链以供审计. 需要此链以防止 CA 通过记录部分或空链逃避责任. (注: 这在找到控制此类证书垃圾信息的某种机制之前, 实际上排除了自签名与基于 DANE 的证书. 作者欢迎建议.)
日志中每个证书条目必须包含以下组件:
enum { x509_entry(0), precert_entry(1), (65535) } LogEntryType;
struct {
LogEntryType entry_type;
select (entry_type) {
case x509_entry: X509ChainEntry;
case precert_entry: PrecertChainEntry;
} entry;
} LogEntry;
opaque ASN.1Cert<1..2^24-1>;
struct {
ASN.1Cert leaf_certificate;
ASN.1Cert certificate_chain<0..2^24-1>;
} X509ChainEntry;
struct {
ASN.1Cert pre_certificate;
ASN.1Cert precertificate_chain<0..2^24-1>;
} PrecertChainEntry;
日志可以限制其愿意接受的链长度.
entry_type 表示此条目的类型. 本协议版本的后续修订可以添加新的 LogEntryType 值. 第 4 节说明客户端应如何处理未知条目类型.
leaf_certificate 为提交审计的终端实体证书.
certificate_chain 为验证终端实体证书所需的附加证书链. 第一个证书必须认证终端实体证书. 每个后续证书必须直接认证紧邻其前的证书. 最终证书必须为日志接受的根证书.
pre_certificate 为提交审计的预证书.
precertificate_chain 为验证预证书提交所需的附加证书链. 链中第一个证书可以为有效的预证书签名证书, 且必须认证预证书 (pre_certificate). 每个后续证书必须直接认证紧邻其前的证书. 最终证书必须为日志接受的根证书.
3.2. Structure of the Signed Certificate Timestamp (签名证书时间戳的结构)
enum { certificate_timestamp(0), tree_hash(1), (255) }
SignatureType;
enum { v1(0), (255) }
Version;
struct {
opaque key_id[32];
} LogID;
opaque TBSCertificate<1..2^24-1>;
struct {
opaque issuer_key_hash[32];
TBSCertificate tbs_certificate;
} PreCert;
opaque CtExtensions<0..2^16-1>;
key_id 为日志公钥的 SHA-256 散列, 在表示为 SubjectPublicKeyInfo 的密钥的 DER 编码上计算.
issuer_key_hash 为证书签发者公钥的 SHA-256 散列, 在表示为 SubjectPublicKeyInfo 的密钥的 DER 编码上计算. 需要此项以将签发者与最终证书绑定.
tbs_certificate 为预证书的 DER 编码 TBSCertificate (见 [RFC5280]) 组件, 即不含签名与毒化扩展. 若预证书并非使用将签发最终证书的 CA 证书签名, 则 TBSCertificate 的 issuer 也会更改为将签发最终证书的 CA. 注意也可以通过从最终证书提取 TBSCertificate 并删除 SCT 扩展来重构此 TBSCertificate. 另请注意, 由于 TBSCertificate 包含 AlgorithmIdentifier, 其必须与预证书签名算法和最终证书签名算法均匹配, 它们必须使用相同算法与参数签名. 若预证书使用预证书签名证书签发且 TBSCertificate 中存在 Authority Key Identifier 扩展, 预证书签名证书中也必须存在相应扩展, 在此情况下, TBSCertificate 的 Authority Key Identifier 也会更改为与最终签发者匹配.
struct {
Version sct_version;
LogID id;
uint64 timestamp;
CtExtensions extensions;
digitally-signed struct {
Version sct_version;
SignatureType signature_type = certificate_timestamp;
uint64 timestamp;
LogEntryType entry_type;
select(entry_type) {
case x509_entry: ASN.1Cert;
case precert_entry: PreCert;
} signed_entry;
CtExtensions extensions;
};
} SignedCertificateTimestamp;
digitally-signed 元素的编码定义见 [RFC5246].
sct_version 为 SCT 所遵循的协议版本. 此版本为 v1.
timestamp 为当前 NTP 时间 [RFC5905], 自纪元 (1970年1月1日 00:00) 起以毫秒计, 忽略闰秒.
entry_type 可由呈现 SCT 的上下文隐式给出.
signed_entry 为上述的 leaf_certificate (对于 X509ChainEntry) 或 PreCert (对于 PrecertChainEntry).
extensions 为本协议版本 (v1) 的未来扩展. 当前未指定任何扩展.
3.3. Including the Signed Certificate Timestamp in the TLS Handshake (在 TLS 握手中包含签名证书时间戳)
来自至少一个日志的与终端实体证书对应的 SCT 数据必须包含在 TLS 握手中, 方式可以是下文所述 X509v3 证书扩展, 类型为 signed_certificate_timestamp 的 TLS 扩展 ([RFC5246] 第 7.4.1.4 节), 或使用在线证书状态协议 (Online Certificate Status Protocol, OCSP) 装订 (亦称 "Certificate Status Request" TLS 扩展; 见 [RFC6066]), 其中响应包含 OID 为 1.3.6.1.4.1.11129.2.4.5 的 OCSP 扩展 (见 [RFC2560]) 及正文:
SignedCertificateTimestampList ::= OCTET STRING
必须至少包含一个 SCT. 服务器运营者可以包含多个 SCT.
同样, 证书颁发机构可以将预证书提交到多个日志, 并将所得全部 SCT 直接嵌入最终证书, 方法是将 SignedCertificateTimestampList 结构编码为 ASN.1 OCTET STRING 并将所得数据作为 X.509v3 证书扩展 (OID 1.3.6.1.4.1.11129.2.4.2) 插入 TBSCertificate. 收到证书后, 客户端可以重构原始 TBSCertificate 以验证 SCT 签名.
嵌入 OCSP 扩展或 X509v3 证书扩展中的 ASN.1 OCTET STRING 内容如下:
opaque SerializedSCT<1..2^16-1>;
struct {
SerializedSCT sct_list <1..2^16-1>;
} SignedCertificateTimestampList;
此处 SerializedSCT 为包含序列化 TLS 结构的不透明字节串. 该编码确保 TLS 客户端可以分别解码每个 SCT (即, 若发生版本升级, 过时的客户端仍可解析旧 SCT 同时跳过不理解的版本的新 SCT).
同样, SCT 可以嵌入 TLS 扩展. 细节见下文.
TLS 客户端必须实现全部三种机制. 服务器必须实现至少一种机制. 注意现有 TLS 服务器通常可以无需修改即使用证书扩展机制.
TLS 服务器宜发送来自多个日志的 SCT, 以防一个或多个日志对客户端不可接受 (例如, 日志因不当行为被除名或密钥已泄露).
3.3.1. TLS Extension (TLS 扩展)
SCT 可以在 TLS 握手期间使用类型为 signed_certificate_timestamp 的 TLS 扩展发送.
支持该扩展的客户端宜在 ClientHello 中发送带适当类型且 extension_data 为空的扩展.
服务器必须仅向已在 ClientHello 中表明支持该扩展的客户端发送 SCT, 此时通过将 extension_data 设为 SignedCertificateTimestampList 来发送 SCT.
会话恢复使用原始会话信息: 客户端宜在 ClientHello 中包含该扩展类型, 但若恢复会话, 服务器不必处理该扩展或在 ServerHello 中包含该扩展.
3.4. Merkle Tree (Merkle 树)
Merkle 树散列的散列算法为 SHA-256.
Merkle 树输入的结构:
enum { timestamped_entry(0), (255) }
MerkleLeafType;
struct {
uint64 timestamp;
LogEntryType entry_type;
select(entry_type) {
case x509_entry: ASN.1Cert;
case precert_entry: PreCert;
} signed_entry;
CtExtensions extensions;
} TimestampedEntry;
struct {
Version version;
MerkleLeafType leaf_type;
select (leaf_type) {
case timestamped_entry: TimestampedEntry;
}
} MerkleTreeLeaf;
此处 version 为 MerkleTreeLeaf 所对应协议的版本. 此版本为 v1.
leaf_type 为叶输入的类型. 当前仅定义了 timestamped_entry (对应 SCT). 本协议版本的后续修订可以添加新的 MerkleLeafType. 第 4 节说明客户端应如何处理未知叶类型.
timestamp 为此证书对应 SCT 的时间戳.
signed_entry 为相应 SCT 的 signed_entry.
extensions 为相应 SCT 的 extensions.
Merkle 树的叶为相应 MerkleTreeLeaf 结构的叶散列.
3.5. Signed Tree Head (签名树头)
每次日志向树追加新条目时, 日志宜对相应的树散列与树信息签名 (见第 4.3 节相应的签名树头客户端消息). 该数据的签名结构如下:
digitally-signed struct {
Version version;
SignatureType signature_type = tree_hash;
uint64 timestamp;
uint64 tree_size;
opaque sha256_root_hash[32];
} TreeHeadSignature;
version 为 TreeHeadSignature 所遵循协议的版本. 此版本为 v1.
timestamp 为当前时间. 时间戳必须至少与树中最近的 SCT 时间戳一样新. 每次后续时间戳必须比上一次更新的时间戳更新.
tree_size 等于新树中的条目数.
sha256_root_hash 为 Merkle 散列树的根.
每个日志必须能够按需产生不早于最大合并延迟的签名树头. 若在一个 MMD 周期内极不可能地未收到新提交, 日志必须以新的时间戳对相同的 Merkle 树散列签名.