DNSSEC 简介

这篇文章最后更新的时间在六个月之前,文章所叙述的内容可能已经失效,请谨慎参考!

看懂这篇文章可能需要的前置知识 DNS ,不对称加密,数字摘要,数字签名

DNSSEC(DNS Security Extensions) 域名系统安全扩展。 开启DNSSEC,可有效防止DNS欺骗和缓存污染等攻击。 它是通过数字签名来保证DNS应答报文的真实性和完整性。

DNSSEC 的工作原理

一般的 DNS

查询 A 记录 -> 返回 A 记录的值

启用了 DNSSEC 的 DNS

查询 A 记录 -> 返回 A 记录的值和 A 记录的 RRSIG

DNSSEC 大致的验证流程

  1. 查询 A 记录
    • 在请求报文里的 Additional 加上 DNSSEC
  2. 从响应报文里获取 RRSIG 记录
  3. 从 RRSIG 记录获取 singer (singer 是一个域名,可能同一级域名也可能是上一级域名)
  4. 查询 singer 的 DNSKEY 记录,获得 ZSK 和 KSK
  5. 使用 ZSK 验证 A 记录的 RRSIG
  6. 如果结果一致,继续查询 singer 的 DS 记录
  7. 对比 KSK 的数字摘要和 DS 记录里的是否一致
  8. 如果结果一致,继续查询 singer 上一级域名的 DNSKEY
  9. 用上一级域名的 ZSK 验证 DS 记录的 RRSIG
  10. 如果结果一致,继续套娃,查询上一级域名的 DS ,直到根域名为止
    • 整个验证的流程和 TLS 的证书链有点相似

让域名支持 DNSSEC

  1. 确认顶级域名支持 DNSSEC
  2. 确认域名注册商支持 DNSSEC
    • 如果当前的域名注册商不支持,就想办法把域名转到能支持的域名注册商
  3. 添加对应的记录
    1. RRSIG (Resource Record Signature) 资源记录签名 该记录用于存放当前域名每一条记录的签名
    2. DNSKEY (DNS Public Key) DNS 公钥
      • DNSKEY 会有两条记录
      • Zone-Signing Key(ZSK) 一般查询的记录由 ZSK 签名
      • Key-Signing Key(KSK) DNSKEY 记录也有 RRSIG ,由 KSK 签名
    3. DS (Delegation Signer) 授权签名
      • KSK 的数字摘要
      • DS 记录也有 RRSIG ,由上一级域名的 ZSK 签名
    4. NSEC (Next Secure) 下一个安全的 用于验证不存在的资源记录
      • 其实笔者没搞明白,这个是用来干什么的

检查网站对 DNSSEC 的支持

DNSSEC 记录

现在的 DNSSEC 至少有 8 种记录类型

下面几个小节主要是描述响应报文里的 Answer 的 RDATA

DNSKEY

Algorithm 的列表 https://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xhtml

rfc 4034 里明确的表示不建议使用 RSA/MD5 作为加密算法

DS

KeyTag 的生成方式

伪代码

unsigned int
keytag (
        unsigned char key[],  /* the RDATA part of the DNSKEY RR */
        unsigned int keysize  /* the RDLENGTH */
        )
{
        unsigned long ac;     /* assumed to be 32 bits or larger */
        int i;                /* loop index */

        for ( ac = 0, i = 0; i < keysize; ++i )
                ac += (i & 1) ? key[i] : key[i] << 8;
        ac += (ac >> 16) & 0xFFFF;
        return ac & 0xFFFF;
}

php

$flags = 257;
$protocol = 3;
$algorithm = 13;
$publicKey = 'mdsswUyr3DPW132mOi8V9xESWE8jTo0dxCjjnopKl+GqJxpVXckHAeF+KkxLbxILfDLUT0rAK9iUzy1L53eKGQ==';

$bin = pack('nCC', $flags, $protocol, $algorithm);
$bin .= base64_decode($publicKey);

$keytag = array_sum(unpack('n*', $bin));
$keytag += ($keytag >> 16) & 0xFFFF;
$keytag = $keytag & 0xFFFF;

Digest 的生成方式

伪代码

digest = digest_algorithm( DNSKEY owner name | DNSKEY RDATA);
DNSKEY RDATA = Flags | Protocol | Algorithm | Public Key.

php

$domainName = 'nslookup.io.';
$flags = 257;
$protocol = 3;
$algorithm = 13;
$publicKey = 'mdsswUyr3DPW132mOi8V9xESWE8jTo0dxCjjnopKl+GqJxpVXckHAeF+KkxLbxILfDLUT0rAK9iUzy1L53eKGQ==';
$publicKey = base64_decode($publicKey);

$domainNameBin = '';
foreach (explode('.', $domainName) as $part) {
    $domainNameBin .= pack('C', strlen($part));
    $domainNameBin .= $part;
}
if (substr($domainName, -1) !== '.') {
    $domainNameBin .= pack('C', 0);
}

$rdata = pack('nCC', $flags, $protocol, $algorithm);
$rdata .= $publicKey;

$data = $domainNameBin . $rdata;

$digest_algo = 'sha256';
$digest = openssl_digest($data, $digest_algo);

RRSIG

DNSSEC 的问题

  1. 只有部分顶级域名支持
  2. 即使顶级域名支持,还需要域名注册商的支持
  3. 无法保证私密性
    • DNSSEC 并没有改变 DNS 基于 UDP 的通讯方式,数据流也都是明文传输, DNSSEC 所做的只是加上了一个数字签名,而中间人依然可以看到你请求了什么、结果是什么
  4. 挟持发生时不能告诉用户真正的记录
  5. 如果要让互联网变得更加安全,那么其它层面的保护(例如 TLS 证书和 DoH )同样重要。

其它

Windows 的 nslookup 不支持 DNSSEC 的记录查询。

PowerShell 的 cmdlet Resolve-DnsName 支持 DNSSEC 的记录查询。

Resolve-DnsName www.nslookup.io -type A -DnssecOk -server 8.8.8.8

最好还是用 dig 命令来检测 DNSSEC 。

dig @8.8.8.8 www.nslookup.io +dnssec

参考