Skip to content

Commit

Permalink
Merge branch '83-private-dns-rr-type-parser'
Browse files Browse the repository at this point in the history
Closes: #83
  • Loading branch information
c0r0n3r committed Mar 5, 2024
2 parents b048895 + 83ba3b0 commit 309ee4f
Show file tree
Hide file tree
Showing 5 changed files with 137 additions and 4 deletions.
49 changes: 49 additions & 0 deletions cryptoparser/common/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -1044,3 +1044,52 @@ def compose(self):
@classmethod
def get_encoding(cls):
return 'utf-8'


@attr.s
class NumericRangeParsableBase(ParsableBase, Serializable):
value = attr.ib(validator=attr.validators.instance_of(six.integer_types))

@value.validator
def _validator_variant(self, _, value):
if value < self._get_value_min():
raise InvalidValue(value, type(self))

if value > self._get_value_max():
raise InvalidValue(value, type(self))

@classmethod
@abc.abstractmethod
def _get_value_min(cls):
raise NotImplementedError()

@classmethod
@abc.abstractmethod
def _get_value_max(cls):
raise NotImplementedError()

@classmethod
@abc.abstractmethod
def _get_value_length(cls):
raise NotImplementedError()

@classmethod
def _parse(cls, parsable):
parser = ParserBinary(parsable)

parser.parse_numeric('value', cls._get_value_length())

return cls(**parser), parser.parsed_length

def compose(self):
composer = ComposerBinary()

composer.compose_numeric(self.value, self._get_value_length())

return composer.composed

def __str__(self):
return str(self.value)

def _as_markdown(self, level):
return self._markdown_result(str(self), level)
28 changes: 24 additions & 4 deletions cryptoparser/dnsrec/record.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@

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

from cryptoparser.common.base import OneByteEnumParsable, Serializable, TwoByteEnumParsable
from cryptoparser.common.base import NumericRangeParsableBase, OneByteEnumParsable, Serializable, TwoByteEnumParsable
from cryptoparser.common.exception import NotEnoughData
from cryptoparser.common.parse import ByteOrder, ComposerBinary, ParsableBase, ParserBinary

Expand Down Expand Up @@ -318,6 +318,20 @@ def compose(self):
raise NotImplementedError()


class DnsRrTypePrivate(NumericRangeParsableBase):
@classmethod
def _get_value_min(cls):
return 0xff00

@classmethod
def _get_value_max(cls):
return 0xfffe

@classmethod
def _get_value_length(cls):
return 2


@attr.s
class DnsNameUncompressed(ParsableBase, Serializable):
labels = attr.ib(
Expand Down Expand Up @@ -373,7 +387,7 @@ def compose(self):
class DnsRecordRrsig(ParsableBase): # pylint: disable=too-many-instance-attributes
HEADER_SIZE = 24

type_covered = attr.ib(validator=attr.validators.instance_of(DnsRrType))
type_covered = attr.ib(validator=attr.validators.instance_of((DnsRrType, DnsRrTypePrivate)))
algorithm = attr.ib(validator=attr.validators.instance_of(DnsSecAlgorithm))
labels = attr.ib(validator=attr.validators.instance_of(six.integer_types))
original_ttl = attr.ib(
Expand All @@ -396,7 +410,10 @@ def _parse(cls, parsable):

parser = ParserBinary(parsable)

parser.parse_parsable('type_covered', DnsRrTypeFactory)
try:
parser.parse_parsable('type_covered', DnsRrTypeFactory)
except InvalidValue:
parser.parse_parsable('type_covered', DnsRrTypePrivate)
parser.parse_parsable('algorithm', DnsSecAlgorithmFactory)
parser.parse_numeric('labels', 1)
parser.parse_numeric('original_ttl', 4)
Expand All @@ -411,7 +428,10 @@ def _parse(cls, parsable):
def compose(self):
composer = ComposerBinary()

composer.compose_numeric_enum_coded(self.type_covered)
if isinstance(self.type_covered, DnsRrType):
composer.compose_numeric_enum_coded(self.type_covered)
else:
composer.compose_parsable(self.type_covered)
composer.compose_numeric_enum_coded(self.algorithm)
composer.compose_numeric(self.labels, 1)
composer.compose_numeric(self.original_ttl, 4)
Expand Down
15 changes: 15 additions & 0 deletions test/common/classes.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
FourByteEnumParsable,
ListParamParsable,
ListParsable,
NumericRangeParsableBase,
OneByteEnumComposer,
OneByteEnumParsable,
OpaqueEnumComposer,
Expand Down Expand Up @@ -789,3 +790,17 @@ def _get_pem_str(self, public_key_file_name):

def _get_public_key_x509(self, public_key_file_name):
return PublicKeyX509.from_pem(self._get_pem_str(public_key_file_name))


class NumericRangeParsableTest(NumericRangeParsableBase):
@classmethod
def _get_value_min(cls):
return 0x01

@classmethod
def _get_value_max(cls):
return 0xfe

@classmethod
def _get_value_length(cls):
return 1
22 changes: 22 additions & 0 deletions test/common/test_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
FourByteEnumParsableTest,
ListParsableTest,
NByteEnumTest,
NumericRangeParsableTest,
OneByteEnumComposerTest,
OneByteEnumParsableTest,
OneByteOddParsable,
Expand Down Expand Up @@ -868,3 +869,24 @@ def test_parse(self):
parsable = bytearray(b'aaa')
self.assertEqual(VariantParsableExactTest.parse_mutable(parsable), StringEnumAAA.AAA)
self.assertEqual(parsable, b'')


class TestNumericRangeParsable(unittest.TestCase):
def test_error(self):
with self.assertRaises(InvalidValue) as context_manager:
NumericRangeParsableTest.parse_exact_size(b'\x00')
self.assertEqual(context_manager.exception.value, 0x00)

with self.assertRaises(InvalidValue) as context_manager:
NumericRangeParsableTest.parse_exact_size(b'\xff')
self.assertEqual(context_manager.exception.value, 0xff)

def test_parse(self):
self.assertEqual(NumericRangeParsableTest.parse_exact_size(b'\x01'), NumericRangeParsableTest(1))
self.assertEqual(NumericRangeParsableTest(1).compose(), b'\x01')

def test_str(self):
self.assertEqual(str(NumericRangeParsableTest(1)), '1')

def test_as_markdown(self):
self.assertEqual(NumericRangeParsableTest(1).as_markdown(), '1')
27 changes: 27 additions & 0 deletions test/dnsrec/test_record.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
DnsRecordMx,
DnsRecordRrsig,
DnsRecordTxt,
DnsRrTypePrivate,
DnsSecFlag,
DnsSecProtocol,
)
Expand Down Expand Up @@ -447,6 +448,30 @@ def setUp(self):
signature=32 * b'\xff',
)

self.record_private_type_bytes = bytes(
b'\xff\xfe' + # type_covered: A
b'\x01' + # algorithm: RSAMD5
b'\x03' + # labels
b'\x00\x00\x0e\x10' + # original_ttl: 3600
b'\x00\x00\x00\x01' + # signature_expiration
b'\x00\x00\x00\x02' + # signature_inception
b'\xab\xcd' + # key_tag
b'\x06signer\x00' + # signers_name
32 * b'\xff' + # signature
b''
)
self.record_private_types = DnsRecordRrsig(
type_covered=DnsRrTypePrivate(0xfffe),
algorithm=DnsSecAlgorithm.RSAMD5,
labels=3,
original_ttl=3600,
signature_expiration=datetime.datetime(1970, 1, 1, 0, 0, 1, tzinfo=dateutil.tz.UTC),
signature_inception=datetime.datetime(1970, 1, 1, 0, 0, 2, tzinfo=dateutil.tz.UTC),
key_tag=0xabcd,
signers_name='signer',
signature=32 * b'\xff',
)

def test_error_not_enough_data(self):
with self.assertRaises(NotEnoughData) as context_manager:
DnsRecordRrsig.parse_exact_size(b'\x00')
Expand All @@ -458,9 +483,11 @@ def test_error_not_enough_data(self):

def test_parse(self):
self.assertEqual(DnsRecordRrsig.parse_exact_size(self.record_bytes), self.record)
self.assertEqual(DnsRecordRrsig.parse_exact_size(self.record_private_type_bytes), self.record_private_types)

def test_compose(self):
self.assertEqual(self.record.compose(), self.record_bytes)
self.assertEqual(self.record_private_types.compose(), self.record_private_type_bytes)


class TestDnsRecordMx(unittest.TestCase):
Expand Down

0 comments on commit 309ee4f

Please sign in to comment.