DNSSEC:签名与信任链
DNSSEC 给 DNS 数据加上数字签名,让验证者能确认“这条记录确实来自区的持有者,而且没被改过”。它不加密,也不保证可用,只回答一个问题:数据是真的吗?
它保护什么,不保护什么#
- 保护:数据来源与完整性。缓存投毒、途中篡改,在验证者那里都会显示为验证失败。
- 保护:经过认证的“不存在”。攻击者无法伪造一个可信的 NXDOMAIN。
- 不保护:机密性。签名的数据仍然以明文传输,任何人都能看。
- 不保护:可用性。攻击者丢掉应答,验证者只会得到 SERVFAIL。
- 不保护:验证者之后的一段。如果 stub 只看解析器给的 AD 位,而到解析器的链路未加密,AD 位本身可以被篡改。
相关记录#
- DNSKEY
- 区的公钥,放在区顶点。flags 字段为 256 表示区签名密钥(ZSK),257 表示带 SEP 标志的密钥,惯例上用作密钥签名密钥(KSK)。KSK 只签 DNSKEY 集合,ZSK 签区里的其他数据——这种分工不是协议要求,而是为了让常换的 ZSK 不必每次都通知父区。
- RRSIG
- 对一个 RRset(同名、同类、同类型的一组记录)的签名。字段包括被签名的类型、算法、标签数、原始 TTL、生效与过期时间、签名密钥的标签(key tag)、签名者名字和签名本身。签名有有效期,过期了即使数据没变也会验证失败。
- DS
- 放在父区里,是子区某个 DNSKEY(通常是 KSK)的摘要。它把父区的信任传给子区,是信任链上的连接点。
- NSEC
- 按规范顺序把区内名字串成链,每条 NSEC 写明“下一个存在的名字”和“本名字上有哪些类型”。查询落在两个名字之间,就由对应的 NSEC 证明不存在。副作用是可以顺着链枚举整个区(zone walking)。
- NSEC3
- 把名字散列后再串链,增加枚举的难度(但离线字典攻击仍然可行)。RFC 9276 建议迭代次数设为 0、不用盐:额外的迭代几乎不增加安全性,却显著增加验证者的计算成本,一些验证者会把迭代次数过高的区当作不安全。
信任链#
验证 www.example.com 的 A 记录,验证者需要走完这样一条链:
- 手里有根区 KSK 的信任锚(内置或经 RFC 5011 更新)。
- 用它验证根区的 DNSKEY 集合,进而信任根区的 ZSK。
- 用根区 ZSK 验证根区里
com.的 DS 记录。 - 拿
com.的 DNSKEY 和这个 DS 比对,信任com.的 KSK,再验证其 DNSKEY 集合。 - 同理验证
com.区里example.com.的 DS,以及example.com.的 DNSKEY 集合。 - 最后用
example.com.的 ZSK 验证www.example.com.A 记录的 RRSIG。
任何一环断开都会改变结论。如果 com. 区里根本没有 example.com. 的 DS(并有签名的不存在证明),说明这个域没有启用 DNSSEC,结论是“不安全”而不是“失败”。
四种验证结果#
| 状态 | 含义 | 验证型解析器的处理 |
|---|---|---|
| Secure | 信任链完整,签名有效 | 返回答案并置 AD=1 |
| Insecure | 有经过验证的证据表明这一段没有签名(缺少 DS 且其不存在已被证明) | 正常返回答案,AD=0 |
| Bogus | 应该有签名却验证失败:签名错误、过期、缺失,或 DS 对不上 | 返回 SERVFAIL,通常附带扩展错误码(EDE 6 至 12) |
| Indeterminate | 无法得出结论,例如缺少必要的信任锚 | 按实现策略处理 |
RFC 4033 §5 定义了这四种状态。客户端发查询时置 CD=1,可以让解析器跳过验证、把原始数据交回——排查“验证失败还是数据本身有问题”时很有用。
算法与摘要类型#
| 编号 | 算法 | 现状 |
|---|---|---|
| 5 | RSASHA1 | 不应再用于签名 |
| 7 | RSASHA1-NSEC3-SHA1 | 不应再用于签名 |
| 8 | RSASHA256 | 使用最广;根区和许多顶级域使用 |
| 10 | RSASHA512 | 可用,使用较少 |
| 13 | ECDSAP256SHA256 | 签名小、验证快,新部署的常见选择 |
| 14 | ECDSAP384SHA384 | 可用,使用较少 |
| 15 | Ed25519 | RFC 8080;签名小,支持面逐年扩大 |
| 16 | Ed448 | RFC 8080;支持面较窄 |
DS 的摘要类型:1 为 SHA-1(不应再新建),2 为 SHA-256(最常用),4 为 SHA-384。各算法的实现要求见 RFC 8624。验证者不支持某个算法时,按规范应把该区视为不安全,而不是失败——所以换用新算法本身不会让老验证者拒绝你的区。
密钥轮换#
密钥要换,换的过程中缓存里还留着旧数据,这是 DNSSEC 运维最容易出错的地方。两种基本做法:
- 预发布(多用于 ZSK):先把新密钥加进 DNSKEY 集合、但暂不用它签名;等旧 DNSKEY 集合在各地缓存里过期后,改用新密钥签名;再等旧签名过期,删除旧密钥。
- 双签(多用于 KSK):新旧密钥同时签 DNSKEY 集合,在父区换上新 DS,等旧 DS 在缓存里过期后撤下旧密钥。
每一步之间的等待时间取决于相关记录的 TTL,而不是你觉得够了就行。KSK 轮换还牵涉父区:传统上要到注册商那里手动换 DS,容易遗漏。CDS 与 CDNSKEY 记录(RFC 7344、RFC 8078)让子区直接发布“请把 DS 改成这个”,支持的父区会自动扫描并更新。
常见故障#
- 签名过期:签名工具停止运行或定时任务失效,几天后整个区在验证型解析器上解析失败。监控 RRSIG 的过期时间应当和监控证书过期一样严格。
- DS 与 DNSKEY 对不上:换了 KSK 却没更新父区的 DS,或者换托管商时把旧 DS 留在了注册商那里。迁移 DNS 托管时,先撤 DS(或换成新托管的 DS 并等待 TTL)再切 NS。
- 时钟偏差:验证者按本机时间判断签名是否生效,时间错了,有效签名也会被判为未生效或已过期。没有电池的小设备开机时尤其容易出现。
- 应答太大:大 RSA 密钥加多个签名让应答超过 UDP 限制,而 TCP/53 又被防火墙挡住,表现为部分网络下间歇失败。
- 验证器被拖垮:2024 年公布的 KeyTrap(CVE-2023-50387)利用大量密钥标签相同的密钥和签名,让验证者进行海量的签名校验。主流解析器已经加入了对验证工作量的限制。
用工具检查#
shell
$ dig +dnssec +multi example.com DNSKEY
$ dig @a.gtld-servers.net +norecurse example.com DS
$ delv @1.1.1.1 example.com A +rtrace
$ dig +cd @1.1.1.1 example.com Adelv 在本机做完整的验证并说明结论,比只看 AD 位可靠得多。+cd 能拿到解析器未验证的原始数据:如果加了 +cd 才能解析,问题几乎一定出在 DNSSEC 上。