实现原理:证书变更检测
对应代码:
pkg/certchange.go· 入口TakeSnapshot/DetectChange
detect-change 通过对比证书快照判断"变了什么、变成什么类型"。本页讲快照结构、7 步对比顺序与变更类型推断。
快照结构
type CertSnapshot struct {
Target string
Timestamp time.Time
CertSHA256 string // 整张证书哈希
SPKISHA256 string // 公钥哈希(续期不变)
Issuer string
NotBefore time.Time
NotAfter time.Time
SerialNumber string
JARMHash string // omitempty
Metadata map[string]string // omitempty
}TakeSnapshot(L61-97)字段填充:
cert.Fingerprints["sha256"]→ CertSHA256["public_key_sha256"]→ SPKISHA256- JARM 走
JARMScan,失败不致命(best effort) ErrCertNotFound当 certs 为空
7 步对比顺序
DetectChange(L100-171)逐字段比对:
变更类型推断
| 场景 | CertSHA256 | SPKI | 类型 |
|---|---|---|---|
| 续期(同公钥新证) | 变 | 不变 | renewed |
| 换钥/换 CA | 变 | 变 | replaced |
| 无变化 | 不变 | 不变 | unchanged |
| 过期 | — | — | expired(叠加) |
| 首次 | — | — | new |
renewed / replaced / expired 的判定关系
renewed 与 replaced 互斥,由 SPKI 是否相同决定。expired 是叠加判定,不覆盖已设的 renewed/replaced——所以一张证书可以同时是 renewed + expired。
核心洞察:用 SPKI 区分"续期"和"换钥"——续期(同公钥、新有效期/新签名)时整张证书变了但公钥没变,SPKI 不变。这正是 SPKI 比 cert SHA-256 更适合"轮换检测"的原因,详见 核心概念·指纹。
持久化:SnapshotStore
文件名格式(L184)
<sanitizeFilename(snap.Target)>_<snap.Timestamp.Format("20060102_150405")>.json例:example_com_20260703_144015.json
ComputeSnapshotID 输入串(L237-244)
ComputeSnapshotID 不含易变字段
ComputeSnapshotID 只哈希 Target + CertSHA256 + SPKISHA256 + SerialNumber,不含 timestamp、issuer、NotBefore/NotAfter、JARM——所以同一证书多次快照会得到相同 ID。这适合做"同一证书"的去重键,不适合做"每次快照"的唯一键。
输出
type CertChangeResult struct {
HasChanged bool
ChangeType string // new/renewed/replaced/expired/unchanged
CurrentSnap *CertSnapshot
PreviousSnap *CertSnapshot
Changes []ChangeDetail
}CertChangeRecord 还预留 revoked 类型——未来可结合 CRL 检测吊销态变化。
与时间线的串联
map-timeline 消费多个快照 JSON,构建完整生命周期。这是 网络空间测绘扫描工作流 的"追踪轮换"阶段。
下一步
- 测绘工作流 → 网络空间测绘扫描工作流
- 指纹体系 → 指纹库匹配
- 离线分析 → 离线 vs 在线分析