Skip to content

Commit 309ee4f

Browse files
committed
Merge branch '83-private-dns-rr-type-parser'
Closes: #83
2 parents b048895 + 83ba3b0 commit 309ee4f

File tree

5 files changed

+137
-4
lines changed

5 files changed

+137
-4
lines changed

cryptoparser/common/base.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1044,3 +1044,52 @@ def compose(self):
10441044
@classmethod
10451045
def get_encoding(cls):
10461046
return 'utf-8'
1047+
1048+
1049+
@attr.s
1050+
class NumericRangeParsableBase(ParsableBase, Serializable):
1051+
value = attr.ib(validator=attr.validators.instance_of(six.integer_types))
1052+
1053+
@value.validator
1054+
def _validator_variant(self, _, value):
1055+
if value < self._get_value_min():
1056+
raise InvalidValue(value, type(self))
1057+
1058+
if value > self._get_value_max():
1059+
raise InvalidValue(value, type(self))
1060+
1061+
@classmethod
1062+
@abc.abstractmethod
1063+
def _get_value_min(cls):
1064+
raise NotImplementedError()
1065+
1066+
@classmethod
1067+
@abc.abstractmethod
1068+
def _get_value_max(cls):
1069+
raise NotImplementedError()
1070+
1071+
@classmethod
1072+
@abc.abstractmethod
1073+
def _get_value_length(cls):
1074+
raise NotImplementedError()
1075+
1076+
@classmethod
1077+
def _parse(cls, parsable):
1078+
parser = ParserBinary(parsable)
1079+
1080+
parser.parse_numeric('value', cls._get_value_length())
1081+
1082+
return cls(**parser), parser.parsed_length
1083+
1084+
def compose(self):
1085+
composer = ComposerBinary()
1086+
1087+
composer.compose_numeric(self.value, self._get_value_length())
1088+
1089+
return composer.composed
1090+
1091+
def __str__(self):
1092+
return str(self.value)
1093+
1094+
def _as_markdown(self, level):
1095+
return self._markdown_result(str(self), level)

cryptoparser/dnsrec/record.py

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020

2121
from cryptodatahub.dnsrec.algorithm import DnsRrType, DnsSecAlgorithm, DnsSecDigestType
2222

23-
from cryptoparser.common.base import OneByteEnumParsable, Serializable, TwoByteEnumParsable
23+
from cryptoparser.common.base import NumericRangeParsableBase, OneByteEnumParsable, Serializable, TwoByteEnumParsable
2424
from cryptoparser.common.exception import NotEnoughData
2525
from cryptoparser.common.parse import ByteOrder, ComposerBinary, ParsableBase, ParserBinary
2626

@@ -318,6 +318,20 @@ def compose(self):
318318
raise NotImplementedError()
319319

320320

321+
class DnsRrTypePrivate(NumericRangeParsableBase):
322+
@classmethod
323+
def _get_value_min(cls):
324+
return 0xff00
325+
326+
@classmethod
327+
def _get_value_max(cls):
328+
return 0xfffe
329+
330+
@classmethod
331+
def _get_value_length(cls):
332+
return 2
333+
334+
321335
@attr.s
322336
class DnsNameUncompressed(ParsableBase, Serializable):
323337
labels = attr.ib(
@@ -373,7 +387,7 @@ def compose(self):
373387
class DnsRecordRrsig(ParsableBase): # pylint: disable=too-many-instance-attributes
374388
HEADER_SIZE = 24
375389

376-
type_covered = attr.ib(validator=attr.validators.instance_of(DnsRrType))
390+
type_covered = attr.ib(validator=attr.validators.instance_of((DnsRrType, DnsRrTypePrivate)))
377391
algorithm = attr.ib(validator=attr.validators.instance_of(DnsSecAlgorithm))
378392
labels = attr.ib(validator=attr.validators.instance_of(six.integer_types))
379393
original_ttl = attr.ib(
@@ -396,7 +410,10 @@ def _parse(cls, parsable):
396410

397411
parser = ParserBinary(parsable)
398412

399-
parser.parse_parsable('type_covered', DnsRrTypeFactory)
413+
try:
414+
parser.parse_parsable('type_covered', DnsRrTypeFactory)
415+
except InvalidValue:
416+
parser.parse_parsable('type_covered', DnsRrTypePrivate)
400417
parser.parse_parsable('algorithm', DnsSecAlgorithmFactory)
401418
parser.parse_numeric('labels', 1)
402419
parser.parse_numeric('original_ttl', 4)
@@ -411,7 +428,10 @@ def _parse(cls, parsable):
411428
def compose(self):
412429
composer = ComposerBinary()
413430

414-
composer.compose_numeric_enum_coded(self.type_covered)
431+
if isinstance(self.type_covered, DnsRrType):
432+
composer.compose_numeric_enum_coded(self.type_covered)
433+
else:
434+
composer.compose_parsable(self.type_covered)
415435
composer.compose_numeric_enum_coded(self.algorithm)
416436
composer.compose_numeric(self.labels, 1)
417437
composer.compose_numeric(self.original_ttl, 4)

test/common/classes.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
FourByteEnumParsable,
3232
ListParamParsable,
3333
ListParsable,
34+
NumericRangeParsableBase,
3435
OneByteEnumComposer,
3536
OneByteEnumParsable,
3637
OpaqueEnumComposer,
@@ -789,3 +790,17 @@ def _get_pem_str(self, public_key_file_name):
789790

790791
def _get_public_key_x509(self, public_key_file_name):
791792
return PublicKeyX509.from_pem(self._get_pem_str(public_key_file_name))
793+
794+
795+
class NumericRangeParsableTest(NumericRangeParsableBase):
796+
@classmethod
797+
def _get_value_min(cls):
798+
return 0x01
799+
800+
@classmethod
801+
def _get_value_max(cls):
802+
return 0xfe
803+
804+
@classmethod
805+
def _get_value_length(cls):
806+
return 1

test/common/test_base.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
FourByteEnumParsableTest,
3636
ListParsableTest,
3737
NByteEnumTest,
38+
NumericRangeParsableTest,
3839
OneByteEnumComposerTest,
3940
OneByteEnumParsableTest,
4041
OneByteOddParsable,
@@ -868,3 +869,24 @@ def test_parse(self):
868869
parsable = bytearray(b'aaa')
869870
self.assertEqual(VariantParsableExactTest.parse_mutable(parsable), StringEnumAAA.AAA)
870871
self.assertEqual(parsable, b'')
872+
873+
874+
class TestNumericRangeParsable(unittest.TestCase):
875+
def test_error(self):
876+
with self.assertRaises(InvalidValue) as context_manager:
877+
NumericRangeParsableTest.parse_exact_size(b'\x00')
878+
self.assertEqual(context_manager.exception.value, 0x00)
879+
880+
with self.assertRaises(InvalidValue) as context_manager:
881+
NumericRangeParsableTest.parse_exact_size(b'\xff')
882+
self.assertEqual(context_manager.exception.value, 0xff)
883+
884+
def test_parse(self):
885+
self.assertEqual(NumericRangeParsableTest.parse_exact_size(b'\x01'), NumericRangeParsableTest(1))
886+
self.assertEqual(NumericRangeParsableTest(1).compose(), b'\x01')
887+
888+
def test_str(self):
889+
self.assertEqual(str(NumericRangeParsableTest(1)), '1')
890+
891+
def test_as_markdown(self):
892+
self.assertEqual(NumericRangeParsableTest(1).as_markdown(), '1')

test/dnsrec/test_record.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
DnsRecordMx,
2929
DnsRecordRrsig,
3030
DnsRecordTxt,
31+
DnsRrTypePrivate,
3132
DnsSecFlag,
3233
DnsSecProtocol,
3334
)
@@ -447,6 +448,30 @@ def setUp(self):
447448
signature=32 * b'\xff',
448449
)
449450

451+
self.record_private_type_bytes = bytes(
452+
b'\xff\xfe' + # type_covered: A
453+
b'\x01' + # algorithm: RSAMD5
454+
b'\x03' + # labels
455+
b'\x00\x00\x0e\x10' + # original_ttl: 3600
456+
b'\x00\x00\x00\x01' + # signature_expiration
457+
b'\x00\x00\x00\x02' + # signature_inception
458+
b'\xab\xcd' + # key_tag
459+
b'\x06signer\x00' + # signers_name
460+
32 * b'\xff' + # signature
461+
b''
462+
)
463+
self.record_private_types = DnsRecordRrsig(
464+
type_covered=DnsRrTypePrivate(0xfffe),
465+
algorithm=DnsSecAlgorithm.RSAMD5,
466+
labels=3,
467+
original_ttl=3600,
468+
signature_expiration=datetime.datetime(1970, 1, 1, 0, 0, 1, tzinfo=dateutil.tz.UTC),
469+
signature_inception=datetime.datetime(1970, 1, 1, 0, 0, 2, tzinfo=dateutil.tz.UTC),
470+
key_tag=0xabcd,
471+
signers_name='signer',
472+
signature=32 * b'\xff',
473+
)
474+
450475
def test_error_not_enough_data(self):
451476
with self.assertRaises(NotEnoughData) as context_manager:
452477
DnsRecordRrsig.parse_exact_size(b'\x00')
@@ -458,9 +483,11 @@ def test_error_not_enough_data(self):
458483

459484
def test_parse(self):
460485
self.assertEqual(DnsRecordRrsig.parse_exact_size(self.record_bytes), self.record)
486+
self.assertEqual(DnsRecordRrsig.parse_exact_size(self.record_private_type_bytes), self.record_private_types)
461487

462488
def test_compose(self):
463489
self.assertEqual(self.record.compose(), self.record_bytes)
490+
self.assertEqual(self.record_private_types.compose(), self.record_private_type_bytes)
464491

465492

466493
class TestDnsRecordMx(unittest.TestCase):

0 commit comments

Comments
 (0)