验证 NSEC3 记录

问题描述

我在摆弄 DNSSEC,我想尝试验证 dnssec-signzonebind9-utils 生成的 NSEC3 记录(我认为这些记录是有效的)。这是我的区域文件

$ORIGIN dnssectest.mvolfik.tk.
$TTL 120
@   SOA dnssectestns.mvolfik.tk. email.example.com. 15 259200 3600 300000 3600
    A   192.168.0.101
s3c A   192.168.0.101

$INCLUDE zsk.key
$INCLUDE ksk.key

ZSK 和 KSK 使用 dnssec-keygen -a ECDSAP256SHA256 dnssectest.mvolfik.tk. 生成(分别添加 -f KSK

然后我使用命令 dnssec-signzone -3 deadbeef -H 5 -o dnssectest.mvolfik.tk -k ksk.key zonefile zsk.key 对其进行签名(使用带有 deadbeef 十六进制盐的 NSEC3,5 次迭代)

我在 zonefile.signed 中得到了以下 NSEC3 记录:(省略了 RRSIG 和 DNSKEY 不相关;A 和 SOA 没有改变)

            0   NSEC3ParaM 1 0 5 DEADBEEF
F66KKS17FM851AVA4EARFHS55I3TOO85.dnssectest.mvolfik.tk. 3600 IN NSEC3 1 0 5 DEADBEEF (
                    D60TA5J5RS4JD5AQK25B1BcuaHGP4DHC
                    A SOA RRSIG DNSKEY NSEC3ParaM )
D60TA5J5RS4JD5AQK25B1BcuaHGP4DHC.dnssectest.mvolfik.tk. 3600 IN NSEC3 1 0 5 DEADBEEF (
                    F66KKS17FM851AVA4EARFHS55I3TOO85
                    A RRSIG )

既然我知道该区域中仅有的域是 s3c.dnssectest.mvolfik.tk.dnssectest.mvolfik.tk.,我假设以下 Python 脚本将为我提供与上面签名区域文件中相同的哈希值:(来自RFC 5155 中的伪代码)

import hashlib
def ih(salt,x,k):
    if k == 0:
        return hashlib.sha1(x + salt).digest()
    return hashlib.sha1(ih(salt,k-1) + salt).digest()

print(ih(bytes.fromhex("deadbeef"),b"s3c.dnssectest.mvolfik.tk.",5).hex())

print(ih(bytes.fromhex("deadbeef"),b"dnssectest.mvolfik.tk.",5).hex())

然而,我得到了 b58374998347ba833ab33f15332829a589a80d82545e01397a776ee73aa0372aea015408cc384574。我做错了什么?

解决方法

所以我查看了 dnspython 源代码,并找到了 nsec3_hash 函数。原来名称必须是有线格式(意味着删除点,而不是前缀标签长度字节 - \x03s3c\x10dnssectest\x07mvolfik\x02tk\x00 等,最后是空字节)。结果用 base32 (0-9A-V) 编码,而不是十六进制。使用 dnspython 库可能更容易,但这里是完整的(有点幼稚)代码:

import hashlib,base64

b32_trans = str.maketrans(
    "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567","0123456789ABCDEFGHIJKLMNOPQRSTUV"
)


def ih(salt,x,k):
    if k == 0:
        return hashlib.sha1(x + salt).digest()
    return hashlib.sha1(ih(salt,k - 1) + salt).digest()


def nsec3(salt,name,k):
    if not name.endswith("."):
        name += "."
    labels = name.split(".")
    name_wire = b"".join(len(l).to_bytes(1,"big") + l.lower().encode() for l in labels)
    digest = ih(bytes.fromhex(salt),name_wire,k)
    return base64.b32encode(digest).decode().translate(b32_trans)

print(nsec3("deadbeef","dnssectest.mvolfik.tk.",5))
print(nsec3("deadbeef","s3c.dnssectest.mvolfik.tk.",5))

这将获得在 NSEC3 记录中看到的正确哈希