Skip to content

Commit

Permalink
DNS: add the DAU, DHU and N3U EDNS(0) options
Browse files Browse the repository at this point in the history
These options are used for signaling cryptographic algorithm
understanding in DNS Security Extensions (DNSSEC):
https://www.rfc-editor.org/rfc/rfc6975.html

The patch was cross-checked with Wireshark:
```sh
>>> dau = EDNS0DAU(alg_code=['RSA/SHA-256', 'RSA/SHA-512'])
>>> dhu = EDNS0DHU(alg_code=['SHA-1', 'SHA-256', 'SHA-384'])
>>> n3u = EDNS0N3U(alg_code=['SHA-1'])
>>> tdecode(Ether()/IP()/UDP()/DNS(ar=[DNSRROPT(rdata=[dau, dhu, n3u])]))
...
            Option: DAU - DNSSEC Algorithm Understood (RFC6975)
                Option Code: DAU - DNSSEC Algorithm Understood (RFC6975) (5)
                Option Length: 2
                Option Data: 080a
                DAU: RSA/SHA-256 (8)
                DAU: RSA/SHA-512 (10)
            Option: DHU - DS Hash Understood (RFC6975)
                Option Code: DHU - DS Hash Understood (RFC6975) (6)
                Option Length: 3
                Option Data: 010204
                DHU: SHA-1 (1)
                DHU: SHA-256 (2)
                DHU: SHA-384 (4)
            Option: N3U - NSEC3 Hash Understood (RFC6975)
                Option Code: N3U - NSEC3 Hash Understood (RFC6975) (7)
                Option Length: 1
                Option Data: 01
                N3U: SHA-1 (1)
```
  • Loading branch information
evverx authored and gpotter2 committed Dec 9, 2023
1 parent a562a06 commit 0dd08cd
Show file tree
Hide file tree
Showing 2 changed files with 130 additions and 32 deletions.
100 changes: 68 additions & 32 deletions scapy/layers/dns.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
ConditionalField,
Field,
FieldLenField,
FieldListField,
FlagsField,
I,
IP6Field,
Expand Down Expand Up @@ -89,6 +90,22 @@
dnsclasses = {1: 'IN', 2: 'CS', 3: 'CH', 4: 'HS', 255: 'ANY'}


# 09/2013 from http://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xhtml # noqa: E501
dnssecalgotypes = {0: "Reserved", 1: "RSA/MD5", 2: "Diffie-Hellman", 3: "DSA/SHA-1", # noqa: E501
4: "Reserved", 5: "RSA/SHA-1", 6: "DSA-NSEC3-SHA1",
7: "RSASHA1-NSEC3-SHA1", 8: "RSA/SHA-256", 9: "Reserved",
10: "RSA/SHA-512", 11: "Reserved", 12: "GOST R 34.10-2001",
13: "ECDSA Curve P-256 with SHA-256", 14: "ECDSA Curve P-384 with SHA-384", # noqa: E501
252: "Reserved for Indirect Keys", 253: "Private algorithms - domain name", # noqa: E501
254: "Private algorithms - OID", 255: "Reserved"}

# 09/2013 from http://www.iana.org/assignments/ds-rr-types/ds-rr-types.xhtml
dnssecdigesttypes = {0: "Reserved", 1: "SHA-1", 2: "SHA-256", 3: "GOST R 34.11-94", 4: "SHA-384"} # noqa: E501

# 12/2023 from https://www.iana.org/assignments/dnssec-nsec3-parameters/dnssec-nsec3-parameters.xhtml # noqa: E501
dnssecnsec3algotypes = {0: "Reserved", 1: "SHA-1"}


def dns_get_str(s, full=None, _ignore_compression=False):
"""This function decompresses a string s, starting
from the given pointer.
Expand Down Expand Up @@ -387,21 +404,25 @@ def i2m(self, pkt, s):
# RFC 2671 - Extension Mechanisms for DNS (EDNS0)

edns0types = {0: "Reserved", 1: "LLQ", 2: "UL", 3: "NSID", 4: "Reserved",
5: "PING", 8: "edns-client-subnet", 10: "COOKIE",
5: "DAU", 6: "DHU", 7: "N3U", 8: "edns-client-subnet", 10: "COOKIE",
15: "Extended DNS Error"}


class EDNS0TLV(Packet):
class _EDNS0Dummy(Packet):
name = "Dummy class that implements extract_padding()"

def extract_padding(self, p):
# type: (bytes) -> Tuple[bytes, Optional[bytes]]
return "", p


class EDNS0TLV(_EDNS0Dummy):
name = "DNS EDNS0 TLV"
fields_desc = [ShortEnumField("optcode", 0, edns0types),
FieldLenField("optlen", None, "optdata", fmt="H"),
StrLenField("optdata", "",
length_from=lambda pkt: pkt.optlen)]

def extract_padding(self, p):
# type: (bytes) -> Tuple[bytes, Optional[bytes]]
return "", p

@classmethod
def dispatch_hook(cls, _pkt=None, *args, **kargs):
# type: (Optional[bytes], *Any, **Any) -> Type[Packet]
Expand All @@ -410,11 +431,7 @@ def dispatch_hook(cls, _pkt=None, *args, **kargs):
if len(_pkt) < 2:
return Raw
edns0type = struct.unpack("!H", _pkt[:2])[0]
if edns0type == 8:
return EDNS0ClientSubnet
if edns0type == 15:
return EDNS0ExtendedDNSError
return EDNS0TLV
return EDNS0OPT_DISPATCHER.get(edns0type, EDNS0TLV)


class DNSRROPT(Packet):
Expand All @@ -432,6 +449,36 @@ class DNSRROPT(Packet):
length_from=lambda pkt: pkt.rdlen)]


# RFC 6975 - Signaling Cryptographic Algorithm Understanding in
# DNS Security Extensions (DNSSEC)

class EDNS0DAU(_EDNS0Dummy):
name = "DNSSEC Algorithm Understood (DAU)"
fields_desc = [ShortEnumField("optcode", 5, edns0types),
FieldLenField("optlen", None, count_of="alg_code", fmt="H"),
FieldListField("alg_code", None,
ByteEnumField("", 0, dnssecalgotypes),
count_from=lambda pkt:pkt.optlen)]


class EDNS0DHU(_EDNS0Dummy):
name = "DS Hash Understood (DHU)"
fields_desc = [ShortEnumField("optcode", 6, edns0types),
FieldLenField("optlen", None, count_of="alg_code", fmt="H"),
FieldListField("alg_code", None,
ByteEnumField("", 0, dnssecdigesttypes),
count_from=lambda pkt:pkt.optlen)]


class EDNS0N3U(_EDNS0Dummy):
name = "NSEC3 Hash Understood (N3U)"
fields_desc = [ShortEnumField("optcode", 7, edns0types),
FieldLenField("optlen", None, count_of="alg_code", fmt="H"),
FieldListField("alg_code", None,
ByteEnumField("", 0, dnssecnsec3algotypes),
count_from=lambda pkt:pkt.optlen)]


# RFC 7871 - Client Subnet in DNS Queries

class ClientSubnetv4(StrLenField):
Expand Down Expand Up @@ -489,7 +536,7 @@ class ClientSubnetv6(ClientSubnetv4):
af_default = b"\x20" # 2000::


class EDNS0ClientSubnet(Packet):
class EDNS0ClientSubnet(_EDNS0Dummy):
name = "DNS EDNS0 Client Subnet"
fields_desc = [ShortEnumField("optcode", 8, edns0types),
FieldLenField("optlen", None, "address", fmt="H",
Expand All @@ -510,9 +557,6 @@ class EDNS0ClientSubnet(Packet):
ClientSubnetv4("address", "192.168.0.0",
length_from=lambda p: p.source_plen))]

def extract_padding(self, p):
return "", p


# RFC 8914 - Extended DNS Errors

Expand Down Expand Up @@ -552,7 +596,7 @@ def extract_padding(self, p):


# https://www.rfc-editor.org/rfc/rfc8914.html
class EDNS0ExtendedDNSError(Packet):
class EDNS0ExtendedDNSError(_EDNS0Dummy):
name = "DNS EDNS0 Extended DNS Error"
fields_desc = [ShortEnumField("optcode", 15, edns0types),
FieldLenField("optlen", None, length_of="extra_text", fmt="!H",
Expand All @@ -561,25 +605,17 @@ class EDNS0ExtendedDNSError(Packet):
StrLenField("extra_text", "",
length_from=lambda pkt: pkt.optlen - 2)]

def extract_padding(self, p):
return "", p


# RFC 4034 - Resource Records for the DNS Security Extensions


# 09/2013 from http://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xhtml # noqa: E501
dnssecalgotypes = {0: "Reserved", 1: "RSA/MD5", 2: "Diffie-Hellman", 3: "DSA/SHA-1", # noqa: E501
4: "Reserved", 5: "RSA/SHA-1", 6: "DSA-NSEC3-SHA1",
7: "RSASHA1-NSEC3-SHA1", 8: "RSA/SHA-256", 9: "Reserved",
10: "RSA/SHA-512", 11: "Reserved", 12: "GOST R 34.10-2001",
13: "ECDSA Curve P-256 with SHA-256", 14: "ECDSA Curve P-384 with SHA-384", # noqa: E501
252: "Reserved for Indirect Keys", 253: "Private algorithms - domain name", # noqa: E501
254: "Private algorithms - OID", 255: "Reserved"}
EDNS0OPT_DISPATCHER = {
5: EDNS0DAU,
6: EDNS0DHU,
7: EDNS0N3U,
8: EDNS0ClientSubnet,
15: EDNS0ExtendedDNSError,
}

# 09/2013 from http://www.iana.org/assignments/ds-rr-types/ds-rr-types.xhtml
dnssecdigesttypes = {0: "Reserved", 1: "SHA-1", 2: "SHA-256", 3: "GOST R 34.11-94", 4: "SHA-384"} # noqa: E501

# RFC 4034 - Resource Records for the DNS Security Extensions

def bitmap2RRlist(bitmap):
"""
Expand Down
62 changes: 62 additions & 0 deletions test/scapy/layers/dns_edns0.uts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,63 @@ def _test():
retry_test(_test)


+ EDNS0 - DAU

= Basic instantiation & dissection

b = b'\x00\x05\x00\x00'

p = EDNS0DAU()
assert raw(p) == b

p = EDNS0DAU(b)
assert p.optcode == 5 and p.optlen == 0 and p.alg_code == []

b = raw(EDNS0DAU(alg_code=['RSA/SHA-256', 'RSA/SHA-512']))

p = EDNS0DAU(b)
repr(p)
assert p.optcode == 5 and p.optlen == 2 and p.alg_code == [8, 10]


+ EDNS0 - DHU

= Basic instantiation & dissection

b = b'\x00\x06\x00\x00'

p = EDNS0DHU()
assert raw(p) == b

p = EDNS0DHU(b)
assert p.optcode == 6 and p.optlen == 0 and p.alg_code == []

b = raw(EDNS0DHU(alg_code=['SHA-1', 'SHA-256', 'SHA-384']))

p = EDNS0DHU(b)
repr(p)
assert p.optcode == 6 and p.optlen == 3 and p.alg_code == [1, 2, 4]


+ EDNS0 - N3U

= Basic instantiation & dissection

b = b'\x00\x07\x00\x00'

p = EDNS0N3U()
assert raw(p) == b

p = EDNS0N3U(b)
assert p.optcode == 7 and p.optlen == 0 and p.alg_code == []

b = raw(EDNS0N3U(alg_code=['SHA-1']))

p = EDNS0N3U(b)
repr(p)
assert p.optcode == 7 and p.optlen == 1 and p.alg_code == [1]


+ EDNS0 - Client Subnet

= Basic instantiation & dissection
Expand Down Expand Up @@ -123,3 +180,8 @@ assert p.info_code == 6 and p.optlen == 45 and p.extra_text == b'proof of non-ex
p = DNSRROPT(raw(DNSRROPT(rdata=[EDNS0ExtendedDNSError(), EDNS0ClientSubnet(), EDNS0TLV()])))
assert len(p.rdata) == 3
assert all(Raw not in opt for opt in p.rdata)

for opt_class in EDNS0OPT_DISPATCHER.values():
p = DNSRROPT(raw(DNSRROPT(rdata=[EDNS0TLV(), opt_class(), opt_class()])))
assert len(p.rdata) == 3
assert all(Raw not in opt for opt in p.rdata)

0 comments on commit 0dd08cd

Please sign in to comment.