跳到主要内容
ecdsa.com
← 全部文章

DNSSEC 信任链:DS、DNSKEY、RRSIG 及常见故障

验证型解析器只信任一个密钥,即根区域的密钥,并沿着已签名的指针逐级向下,直到应答。每一次 DNSSEC 故障,都是这条链上断了一环。本文先用 dig 和 delv 走一遍真实的信任链,再在实验环境中故意弄断信任链,展示每种错误的表现。

简短回答:DNSSEC 用两种记录类型把各个区域连接起来。父区域中的 DS 记录保存子区域密钥签名密钥(DNSKEY,标志 257)的哈希。这个密钥对子区域的 DNSKEY 记录集签名,区域签名密钥(标志 256)则用 RRSIG 记录对实际数据签名。验证型解析器从它信任的根密钥出发,逐一检查通往应答的每个环节。只要有一个环节验证不通过,它就返回 SERVFAIL,而不是数据。检查信任链可以用 delv(看“fully validated”)或 dig +dnssec(看 ad 标志);用 dnssec-dsfromkey 比对父区域的 DS 与子区域的 DNSKEY;用 +cd 查看数据是否其实存在、只是没有通过验证。大多数故障源于密钥轮换:更换 KSK 后没有更新 DS,签名被放任过期,或者密钥更换的速度快于缓存过期的速度。

四种记录

记录所在位置内容
DNSKEY区域本身公钥。标志 257 表示密钥签名密钥(KSK,设置了“SEP”位),256 表示区域签名密钥(ZSK)
RRSIG每个已签名的记录集旁边由一个密钥对一个记录集(RRset)所做的签名,带有起始时间、过期时间和签名密钥的标签
DS父区域中,位于委托点子区域某个 DNSKEY 的摘要:密钥标签、算法、摘要类型、摘要
NSEC / NSEC3区域中经过签名的证明,表明某个名称或类型不存在

这种设计按职责划分密钥。KSK 只对 DNSKEY 记录集签名,它的哈希存放在父区域中。更换 KSK 就要和父区域(注册商或注册局)打交道。ZSK 对其余所有内容签名,更换时无需父区域参与。区域也可以用一个组合密钥(CSK)同时承担两种职责,BIND 内置的 default dnssec-policy 策略就是这样做的(csk ... algorithm 13)。规则不变,只是这个密钥的标志为 257,并对所有内容签名。

用 dig 逐级查看一条真实的信任链

以 www.isc.org 为例,于2026年10月20日逐步查询(使用 BIND 9.20.10 的 dig,查询发往 1.1.1.1)。首先是根区域,它的 DNSKEY 记录集中有一个 ZSK 和两个 KSK:

dig . DNSKEY +multiline(节选)
.			2158 IN	DNSKEY 256 3 8 (
) ; ZSK; alg = RSASHA256 ; key id = 8763
.			2158 IN	DNSKEY 257 3 8 (
) ; KSK; alg = RSASHA256 ; key id = 20326
.			2158 IN	DNSKEY 257 3 8 (
) ; KSK; alg = RSASHA256 ; key id = 38696

密钥 20326 是 KSK-2017,38696 是 KSK-2024。根 KSK 轮换发生在这次查询的 9 天前。用 ICANN 的话说:“2026年10月11日 14:00 UTC,KSK-2024 按计划开始为 DNS 根区域签名。”根 DNSKEY 记录集上的签名证实了这一点:

dig . DNSKEY +dnssec:根密钥集上的签名
.			1587 IN	RRSIG DNSKEY 8 0 172800 (
20261101000000 20261011000000 38696 .

这是该记录集上唯一的 RRSIG。旧密钥在计划吊销之前仍会继续发布,但已不再签名。如果解析器既没有通过 RFC 5011 自动更新、也没有通过软件更新得知 KSK-2024,因而只信任 KSK-2017,那么它就再也无法验证任何内容。

接下来是每一级委托。父区域的 DS 通过密钥标签指明子区域的 KSK:

dig org. DS、dig org. DNSKEY、dig isc.org. DS、dig isc.org. DNSKEY(节选)
org.			81000	IN	DS	26974 8 2 4FEDE294C53F438A158C41D39489CD78A86BEB0D8A0AEAFF14745C0D 16E1DE32
) ; ZSK; alg = RSASHA256 ; key id = 25488
) ; ZSK; alg = RSASHA256 ; key id = 11859
) ; ZSK; alg = RSASHA256 ; key id = 24060
) ; KSK; alg = RSASHA256 ; key id = 725
) ; KSK; alg = RSASHA256 ; key id = 26974
isc.org.		3600	IN	DS	7250 13 2 A30B3F78B6DDE9A4A9A2AD0C805518B4F49EC62E7D3F4531D33DE697 CDA01CB2
) ; ZSK; alg = ECDSAP256SHA256 ; key id = 27566
) ; KSK; alg = ECDSAP256SHA256 ; key id = 7250

根区域中 org. 的 DS 指向密钥 26974,org. 将这个密钥作为 KSK 发布。org. 中 isc.org. 的 DS 指向密钥 7250,即 isc.org 的 KSK。注意沿途的算法变化:org. 用 RSA 签名(算法 8),isc.org 用 ECDSA P-256(算法 13)。每个区域自行选择算法。最后是应答,以及 isc.org 的 ZSK 27566 对它的签名:

dig www.isc.org. A +dnssec +multiline
www.isc.org.		300 IN A 151.101.3.42
www.isc.org.		300 IN A 151.101.67.42
www.isc.org.		300 IN A 151.101.131.42
www.isc.org.		300 IN A 151.101.195.42
www.isc.org.		300 IN RRSIG A 13 3 300 (
				20261103074926 20261020065242 27566 isc.org.
				qz5Qb4BFyOw4PRXBJjeK61LrPSKo6G2Ma1aFwNwZ57x5
				bFIg/d3EoMHtXopLYU949QQtMjcQ8BWULk7a629tLA== )

RRSIG 的字段依次为:所覆盖的类型 A、算法 13、3 个标签、原始 TTL 300、过期时间为2026年11月3日 07:49:26 UTC、起始时间为2026年10月20日 06:52:42 UTC、密钥标签 27566、签名者 isc.org.。验证型解析器检查完这一切后,会设置 AD(authenticated data,已验证数据)标志:

dig www.isc.org A +dnssec @1.1.1.1
;; flags: qr rd ra ad; QUERY: 1, ANSWER: 5, AUTHORITY: 0, ADDITIONAL: 1

ad 只是解析器的声明,它的可信程度取决于你与解析器之间的路径。要自行验证,请使用 delv:它以内置的根信任锚为起点,在本地完成整条链的验证。+rtrace 会列出它发出的各次获取请求:

delv @1.1.1.1 www.isc.org A +rtrace(BIND 9.20.10,节选)
;; fetch: www.isc.org/A
;; fetch: isc.org/DNSKEY
;; fetch: isc.org/DS
;; fetch: org/DNSKEY
;; fetch: org/DS
;; fetch: ./DNSKEY
; fully validated

旧指南推荐的 dig +sigchase 已被移除:dig 9.20 会报错 dig: +sigchase option no longer supported。请改用 delv。如果想在浏览器中通过 DNS-over-HTTPS 完成同样的逐级验证,DNSSEC 查看器会获取每个环节,用自己的代码验证每条 RRSIG,并指出断开的那一环。

算法 8、13 和 15

DNSKEY、DS 和 RRSIG 记录中都会出现算法编号。RFC 9904(2025年11月)取代了 RFC 8624,并把各项推荐直接写进了 IANA 注册表本身;与它同时发布的 RFC 9905 禁止使用两种 SHA-1 算法签名:

编号算法签名验证
5、7RSA/SHA-1不得使用(MUST NOT)必须实现(MUST);解析器运营方将其视为不支持的算法
8RSA/SHA-256推荐(RECOMMENDED),必须实现(MUST)必须实现(MUST)
10RSA/SHA-512不推荐(NOT RECOMMENDED)必须实现(MUST)
13ECDSA P-256/SHA-256推荐(RECOMMENDED),必须实现(MUST)必须实现(MUST)
14ECDSA P-384/SHA-384可以使用(MAY)推荐(RECOMMENDED)
15Ed25519推荐(RECOMMENDED)推荐(RECOMMENDED)
16Ed448可以使用(MAY)推荐(RECOMMENDED)

DS 摘要应使用 SHA-256(类型 2)。对于委托,SHA-1(类型 1)属于“不得使用(MUST NOT)”,不过验证器仍然必须支持它。在我们于2026年10月20日检查的 10 个区域中,com.、net.、nl.、cloudflare.com、isc.org、ietf.org、nic.cz 和 sidn.nl 使用算法 13 签名,而 org. 和 de. 与根区域一样使用算法 8。用每种算法分别对同一区域签名,就能看出 13 为何受欢迎:

dnssec-keygen / dnssec-signzone 9.20.29:同一区域,三种算法
algorithm 8  RSASHA256         ZSK public key 260 bytes, RRSIG signature 256 bytes
algorithm 13 ECDSAP256SHA256   ZSK public key  64 bytes, RRSIG signature  64 bytes
algorithm 15 ED25519           ZSK public key  32 bytes, RRSIG signature  64 bytes

签名小,响应就能保持在 UDP 大小限制以内(如今常用的 EDNS 缓冲区大小为 1232 字节),从而避免分片和回退到 TCP。算法 15 的密钥最小,但验证器对它的支持只是“推荐(RECOMMENDED)”,而不是“必须(MUST)”,所以选择它之前,先确认你的受众所用的解析器是否支持。

故意弄断一条信任链

为了在不破坏真实域名的情况下观察各种故障,下面的实验环境在 Debian 13 上运行两个 BIND 9.20.29 实例。一个是权威服务器,为私有根区域 . 以及 lab. 和 shop.lab. 提供服务,全部使用算法 13 签名。另一个是验证型解析器,它唯一的信任锚是这个私有根区域的 DS。生成密钥和签名的步骤都是普通的 BIND 工具操作:

实验环境节选:密钥、签名以及解析器的信任锚
keygen() { dnssec-keygen -q -K keys -a ECDSAP256SHA256 -L 3600 "$@"; }
for z in . lab shop.lab; do keygen -fKSK "$z" >/dev/null; keygen "$z" >/dev/null; done

# the parent includes the child's dsset file before signing
cat auth/db.lab.unsigned auth/dsset-shop.lab. > auth/db.lab
dnssec-signzone -q -S -K keys -d auth -o lab -f auth/db.lab.signed auth/db.lab

read -r _ _ _ tag alg dt d1 d2 < auth/dsset-.
echo "trust-anchors { . static-ds $tag $alg $dt \"$d1$d2\"; };" > anchors.conf

1. 一切正常。lab. 中的 DS 指向 shop.lab. 的 KSK,解析器设置了 ad,delv 验证通过:

实验环境,用例 1
DS in lab.: shop.lab. 48794 13 2
DNSKEY in shop.lab.: ZSK; alg = ECDSAP256SHA256 ; key id = 6251
DNSKEY in shop.lab.: KSK; alg = ECDSAP256SHA256 ; key id = 48794
$ dig @127.0.0.1 www.shop.lab A +dnssec
;; ->>HEADER<<- opcode: QUERY, status: NOERROR
;; flags: qr rd ra ad; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1
www.shop.lab.		5	IN	A	192.0.2.10
$ delv @127.0.0.2 -a anchors.conf www.shop.lab A
; fully validated
www.shop.lab.		5	IN	A	192.0.2.10

2. 子区域更换了 KSK,父区域中的 DS 没有更新。这是一种典型故障,常见于迁移 DNS 托管之后,或者自动 KSK 轮换在注册商那一步无人完成时:

实验环境,用例 2
DS in lab.: shop.lab. 48794 13 2
DNSKEY in shop.lab.: KSK; alg = ECDSAP256SHA256 ; key id = 11943
DNSKEY in shop.lab.: ZSK; alg = ECDSAP256SHA256 ; key id = 6251
$ dig @127.0.0.1 www.shop.lab A +dnssec
;; ->>HEADER<<- opcode: QUERY, status: SERVFAIL
;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1
$ dig @127.0.0.1 www.shop.lab A +dnssec +cd
;; ->>HEADER<<- opcode: QUERY, status: NOERROR
;; flags: qr rd ra cd; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1
www.shop.lab.		5	IN	A	192.0.2.10
$ delv @127.0.0.2 -a anchors.conf www.shop.lab A
;; validating shop.lab/DNSKEY: no valid signature found (DS)
;; broken trust chain resolving 'www.shop.lab/A/IN': 127.0.0.2#53
;; resolution failed: broken trust chain

DS 仍然写着 48794,而区域现在的 KSK 是 11943。+cd(checking disabled,禁用校验)会返回数据,这证明区域本身在线,只是验证失败。这是区分 DNSSEC 验证失败与真正服务中断的最快方法。

3. 签名过期。DS 已经修正,但区域只签过一次名,之后再也没有重新签名。实验中签名时把起始时间设为 10 天前,过期时间设为 1 小时前:

实验环境,用例 3
RRSIG inception 20261010131152 expiration 20261020121152
$ dig @127.0.0.1 www.shop.lab A +dnssec
;; ->>HEADER<<- opcode: QUERY, status: SERVFAIL
;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1
; EDE: 7 (Signature Expired): (shop.lab/DNSKEY)
$ delv @127.0.0.2 -a anchors.conf www.shop.lab A
;; validating shop.lab/DNSKEY: no valid signature found (DS)
;; broken trust chain resolving 'www.shop.lab/A/IN': 127.0.0.2#53
;; resolution failed: broken trust chain

BIND 会附上一个扩展 DNS 错误(Extended DNS Error,RFC 8914),指明原因:EDE: 7 (Signature Expired)。用 dnssec-signzone 静态签名时,签名默认有效期为 30 天(实验环境第一次签名生成的起始时间为 20261020120639,过期时间为 20261119120639),因此一旦负责重新签名的 cron 任务停止运行,一个月后就会出现完全相同的问题。应改为让 DNS 服务器自动签名和重新签名:使用 BIND 的 dnssec-policy(默认每 5 天刷新一次有效期为 14 天的签名),或者 Knot、PowerDNS 中的对应功能。

4. 一步替换 ZSK。区域更换 ZSK,并立即用新密钥重新签名所有内容。缓存了旧 DNSKEY 记录集(TTL 3600)的解析器会收到一条由它没有的密钥签名的 A 记录:

实验环境,用例 4
$ dig @127.0.0.1 www.shop.lab A +dnssec
;; ->>HEADER<<- opcode: QUERY, status: NOERROR
;; flags: qr rd ra ad; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1
www.shop.lab.		5	IN	A	192.0.2.10
resolver cache: DNSKEY flags 256 TTL 3600
resolver cache: DNSKEY flags 257 TTL 3600
(6 s later: the A record's 5 s TTL has expired, the cached DNSKEY set's 3600 s TTL has not)
$ dig @127.0.0.1 www.shop.lab A +dnssec
;; ->>HEADER<<- opcode: QUERY, status: SERVFAIL
;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1
(resolver restarted, cache empty)
$ dig @127.0.0.1 www.shop.lab A +dnssec
;; ->>HEADER<<- opcode: QUERY, status: NOERROR
;; flags: qr rd ra ad; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1
www.shop.lab.		5	IN	A	192.0.2.10

区域本身是有效的:缓存为空的解析器能验证通过。但每个缓存了旧密钥集的解析器都会验证失败,直到这条缓存过期为止。这种故障时有时无,而且因解析器而异,是最难排查的一类。

不会出问题的密钥轮换

RFC 6781 描述了安全的操作顺序。它们背后的共同规则是:无论解析器缓存了新旧记录的何种组合,都必须能完成验证。

  • ZSK:预发布。在旧 ZSK 仍在签名时,把新 ZSK 加入 DNSKEY 记录集。至少等待一个 DNSKEY TTL,让每个缓存都拿到两个密钥。然后把签名切换到新密钥。等待已签名数据中最长的 TTL 过去,让旧签名从缓存中清空。最后删除旧密钥。
  • KSK:双签名。添加新 KSK,并用新旧两个 KSK 对 DNSKEY 记录集签名。向父区域提交新的 DS。等到父区域发布新 DS,且旧 DS 已从缓存中过期(即 DS 的 TTL,通常为一小时或一天)。只有到那时才能删除旧 KSK。
  • 算法轮换(例如从 8 换到 13)。新算法的新密钥必须在新 DS 出现之前就对所有内容签名,而旧算法必须一直签名,直到旧 DS 消失为止。完整步骤见 RFC 6781 §4.1.4。
  • 让父区域这一步自动化。CDS 和 CDNSKEY 记录(RFC 8078)让支持它们的父区域能从子区域获取新的 DS。如果没有这种机制,DS 这一步就要在注册商那里手动完成,而这正是轮换最常卡住的地方。
  • 更换 DNS 服务商。要么进行协调一致的多签名者(multi-signer)迁移,要么先在注册商处删除 DS,等待其 TTL 过去,再用新服务商的密钥重新启用 DNSSEC。在旧 DS 仍然存在时修改 NS 记录,就等于在生产环境中重现用例 2。

BIND 的 dnssec-policy 以及 Knot 和 PowerDNS 中的密钥管理器会替你按这些时间要求执行。常见的失败点不在工具,而在父区域这一步:一条必须粘贴到注册商网页表单中的 DS 记录。

排查 SERVFAIL

  1. 是不是 DNSSEC 的问题?dig name +cd 返回数据,而 dig name 返回 SERVFAIL → 验证失败。查找 EDE 行:6(DNSSEC Bogus)、7(Signature Expired)、8(Signature Not Yet Valid)、9(DNSKEY Missing)、10(RRSIGs Missing)。
  2. 哪个环节出了问题?运行 delv name +rtrace,查看第一个失败的步骤。
  3. DS 与 DNSKEY 对比:根据子区域的密钥计算 DS,并与父区域中的 DS 比对:
父区域中的 DS 与根据子区域 DNSKEY 计算出的 DS(bind9-utils 9.20.29)
$ dig +short DS isc.org @1.1.1.1
7250 13 2 A30B3F78B6DDE9A4A9A2AD0C805518B4F49EC62E7D3F4531D33DE697 CDA01CB2
$ dig +cd +noall +answer DNSKEY isc.org @1.1.1.1 | dnssec-dsfromkey -2 -f - isc.org
isc.org. IN DS 7250 13 2 A30B3F78B6DDE9A4A9A2AD0C805518B4F49EC62E7D3F4531D33DE697CDA01CB2
$ dig +short DS dnssec-failed.org @1.1.1.1
42069 13 2 62726F6B656E20636861696E206F662074727573742073656E642068 656C7021
$ dig +cd +noall +answer DNSKEY dnssec-failed.org @1.1.1.1 | dnssec-dsfromkey -2 -f - dnssec-failed.org
dnssec-failed.org. IN DS 50719 13 2 D602C971A02AF89B7661B99D6DD1B9E2EC876617631D04F633902A42CD4D6BBD

isc.org 匹配。dnssec-failed.org 是一个故意配置错误的测试域名,由 Comcast 的域名服务器(dns101.comcast.net 等)提供服务。它的 DS 指向密钥 42069,而这个密钥并不存在;摘要则是“broken chain of trust send help!”的 ASCII 编码。公共解析器因此拒绝了它:

dig @1.1.1.1 dnssec-failed.org A
;; ->>HEADER<<- opcode: QUERY, status: SERVFAIL, id: 59477
; EDE: 9 (DNSKEY Missing): (no SEP matching the DS found for dnssec-failed.org.)
  1. 签名时间:将 RRSIG 的起始时间和过期时间与当前 UTC 时间比较。签名者或验证器的时钟不准,就会出现“尚未生效”或“已过期”。
  2. 因解析器而异的故障如果出现在密钥变更之后,说明问题出在缓存(用例 4)。检查 DNSKEY 和 DS 记录集的 TTL,以及变更发生的时间。

DNSSEC 还能支撑什么

对于需要真实性、而不仅仅是可用性的记录,一条正常工作的信任链是基础。DANE/TLSA 在 DNS 中固定 TLS 密钥,用于邮件服务器之间的 SMTP。TLSA 记录生成器可以生成这类记录。CA 会检查 CAA 记录,而且自2026年3月15日起,CA/Browser Forum 的《基线要求》规定,只要区域已签名,CAA 查询就必须经过 DNSSEC 验证。因此,信任链断裂也可能导致证书无法签发。

参考资料

真实环境中的查询于2026年10月20日在 nicolaka/netshoot:v0.14 中运行(BIND 9.20.10 的 dig 和 delv)。实验环境和 dnssec-dsfromkey 在 debian:trixie-20261005-slim 中运行,使用 Debian 提供的 BIND 9.20.29。