Skip to content

Commit 3944181

Browse files
committed
feat(dnssec)!: Implement parser for DNSSEC DS records (#72)
1 parent c0bbbba commit 3944181

File tree

2 files changed

+82
-4
lines changed

2 files changed

+82
-4
lines changed

cryptoparser/dnsrec/record.py

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import enum
66

77
import attr
8+
import six
89

910
from cryptodatahub.common.algorithm import Authentication, NamedGroup, Signature
1011
from cryptodatahub.common.exception import InvalidValue
@@ -15,11 +16,12 @@
1516
PublicKeyParamsEddsa,
1617
PublicKeyParamsRsa,
1718
)
18-
from cryptodatahub.dnssec.algorithm import DnsSecAlgorithm
19+
20+
from cryptodatahub.dnssec.algorithm import DnsSecAlgorithm, DnsSecDigestType
1921

2022
from cryptoparser.common.base import OneByteEnumParsable, Serializable
2123
from cryptoparser.common.exception import NotEnoughData
22-
from cryptoparser.common.parse import ByteOrder, ParsableBase, ParserBinary, ComposerBinary
24+
from cryptoparser.common.parse import ByteOrder, ComposerBinary, ParsableBase, ParserBinary
2325

2426

2527
class DnsSecProtocol(enum.Enum):
@@ -257,3 +259,47 @@ def compose(self):
257259
key_bytes = self.compose_key(self.key)
258260

259261
return composer.composed_bytes + key_bytes
262+
263+
264+
class DnsSecDigestTypeFactory(OneByteEnumParsable):
265+
@classmethod
266+
def get_enum_class(cls):
267+
return DnsSecDigestType
268+
269+
@abc.abstractmethod
270+
def compose(self):
271+
raise NotImplementedError()
272+
273+
274+
@attr.s
275+
class DnsRecordDs(ParsableBase):
276+
HEADER_SIZE = 4
277+
278+
key_tag = attr.ib(validator=attr.validators.instance_of(six.integer_types))
279+
algorithm = attr.ib(validator=attr.validators.instance_of(DnsSecAlgorithm))
280+
digest_type = attr.ib(validator=attr.validators.instance_of(DnsSecDigestType))
281+
digest = attr.ib(validator=attr.validators.instance_of((bytes, bytearray)))
282+
283+
@classmethod
284+
def _parse(cls, parsable):
285+
if len(parsable) < cls.HEADER_SIZE:
286+
raise NotEnoughData(cls.HEADER_SIZE - len(parsable))
287+
288+
parser = ParserBinary(parsable)
289+
290+
parser.parse_numeric('key_tag', 2)
291+
parser.parse_parsable('algorithm', DnsSecAlgorithmFactory)
292+
parser.parse_parsable('digest_type', DnsSecDigestTypeFactory)
293+
parser.parse_raw('digest', parser.unparsed_length)
294+
295+
return cls(**parser), parser.parsed_length
296+
297+
def compose(self):
298+
composer = ComposerBinary()
299+
300+
composer.compose_numeric(self.key_tag, 2)
301+
composer.compose_numeric_enum_coded(self.algorithm)
302+
composer.compose_numeric_enum_coded(self.digest_type)
303+
composer.compose_raw(self.digest)
304+
305+
return composer.composed_bytes

test/dnsrec/test_record.py

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,10 @@
1515
PublicKeyParamsRsa,
1616
)
1717

18-
from cryptodatahub.dnssec.algorithm import DnsSecAlgorithm
18+
from cryptodatahub.dnssec.algorithm import DnsSecAlgorithm, DnsSecDigestType
1919

2020
from cryptoparser.common.exception import NotEnoughData
21-
from cryptoparser.dnsrec.record import DnsRecordDnskey, DnsSecFlag, DnsSecProtocol
21+
from cryptoparser.dnsrec.record import DnsRecordDnskey, DnsRecordDs, DnsSecFlag, DnsSecProtocol
2222

2323

2424
class TestDnsRecordDnskey(unittest.TestCase):
@@ -330,3 +330,35 @@ def test_real(self):
330330
protocol=DnsSecProtocol.V3,
331331
)
332332
self.assertEqual(dns_record.key_tag, 59732)
333+
334+
335+
class TestDnsRecordDs(unittest.TestCase):
336+
def setUp(self):
337+
self.record_bytes = bytes(
338+
b'\x00\x01' + # key_tag: 1
339+
b'\x01' + # algorithm: RSAMD5
340+
b'\x02' + # digest_type: SHA_256
341+
32 * b'\xff' + # digest
342+
b''
343+
)
344+
self.record = DnsRecordDs(
345+
key_tag=1,
346+
algorithm=DnsSecAlgorithm.RSAMD5,
347+
digest_type=DnsSecDigestType.SHA_256,
348+
digest=32 * b'\xff',
349+
)
350+
351+
def test_error_not_enough_data(self):
352+
with self.assertRaises(NotEnoughData) as context_manager:
353+
DnsRecordDs.parse_exact_size(b'\x00')
354+
355+
self.assertEqual(
356+
context_manager.exception.bytes_needed,
357+
DnsRecordDs.HEADER_SIZE - 1
358+
)
359+
360+
def test_parse(self):
361+
self.assertEqual(DnsRecordDs.parse_exact_size(self.record_bytes), self.record)
362+
363+
def test_compose(self):
364+
self.assertEqual(self.record.compose(), self.record_bytes)

0 commit comments

Comments
 (0)