BER encoding
------------
-.. warning::
-
- Currently BER support is not extensively tested.
-
By default PyDERASN accepts only DER encoded data. It always encodes to
DER. But you can optionally enable BER decoding with setting ``bered``
:ref:`context <ctx>` argument to True. Indefinite lengths and
len(value) * 4,
hexdec(value + ("" if len(value) % 2 == 0 else "0")),
)
- else:
- raise InvalidValueType((self.__class__, string_types, binary_type))
- elif isinstance(value, binary_type):
+ if isinstance(value, binary_type):
return (len(value) * 8, value)
else:
raise InvalidValueType((self.__class__, string_types, binary_type))
decode_path=decode_path,
offset=offset,
)
- if l > 0 and l > len(v):
+ if l > len(v):
raise NotEnoughData(
"encoded length is longer than data",
klass=self.__class__,
raise DecodeError(
"chunk out of bounds",
klass=self.__class__,
- decode_path=len(chunks) - 1,
+ decode_path=decode_path + (str(len(chunks) - 1),),
offset=chunks[-1].offset,
)
sub_decode_path = decode_path + (str(len(chunks)),)
for chunk_i, chunk in enumerate(chunks[:-1]):
if chunk.bit_len % 8 != 0:
raise DecodeError(
- "BitString chunk is not multiple of 8 bit",
+ "BitString chunk is not multiple of 8 bits",
klass=self.__class__,
decode_path=decode_path + (str(chunk_i),),
offset=chunk.offset,
decode_path=decode_path,
offset=offset,
)
- if l > 0 and l > len(v):
+ if l > len(v):
raise NotEnoughData(
"encoded length is longer than data",
klass=self.__class__,
decode_path=decode_path,
offset=offset,
)
- if not lenindef and l == 0:
- raise NotEnoughData(
- "zero length",
- klass=self.__class__,
- decode_path=decode_path,
- offset=offset,
- )
chunks = []
sub_offset = offset + tlen + llen
vlen = 0
raise DecodeError(
"chunk out of bounds",
klass=self.__class__,
- decode_path=len(chunks) - 1,
+ decode_path=decode_path + (str(len(chunks) - 1),),
offset=chunks[-1].offset,
)
sub_decode_path = decode_path + (str(len(chunks)),)
sub_offset += chunk.tlvlen
vlen += chunk.tlvlen
v = v_tail
- if len(chunks) == 0:
- raise DecodeError(
- "no chunks",
- klass=self.__class__,
- decode_path=decode_path,
- offset=offset,
- )
try:
obj = self.__class__(
value=b"".join(bytes(chunk) for chunk in chunks),
llen, vlen, v = 1, 0, lv[1:]
sub_offset = offset + tlen + llen
chunk_i = 0
- while True:
- if v[:EOC_LEN].tobytes() == EOC:
- tlvlen = tlen + llen + vlen + EOC_LEN
- obj = self.__class__(
- value=tlv[:tlvlen].tobytes(),
- expl=self._expl,
- optional=self.optional,
- _decoded=(offset, 0, tlvlen),
- )
- obj.lenindef = True
- obj.tag = t
- return obj, v[EOC_LEN:]
- else:
- chunk, v = Any().decode(
- v,
- offset=sub_offset,
- decode_path=decode_path + (str(chunk_i),),
- leavemm=True,
- ctx=ctx,
- )
- vlen += chunk.tlvlen
- sub_offset += chunk.tlvlen
- chunk_i += 1
+ while v[:EOC_LEN].tobytes() != EOC:
+ chunk, v = Any().decode(
+ v,
+ offset=sub_offset,
+ decode_path=decode_path + (str(chunk_i),),
+ leavemm=True,
+ ctx=ctx,
+ )
+ vlen += chunk.tlvlen
+ sub_offset += chunk.tlvlen
+ chunk_i += 1
+ tlvlen = tlen + llen + vlen + EOC_LEN
+ obj = self.__class__(
+ value=tlv[:tlvlen].tobytes(),
+ expl=self._expl,
+ optional=self.optional,
+ _decoded=(offset, 0, tlvlen),
+ )
+ obj.lenindef = True
+ obj.tag = t
+ return obj, v[EOC_LEN:]
except DecodeError as err:
raise err.__class__(
msg=err.msg,
_decoded=(offset, llen, vlen + (EOC_LEN if lenindef else 0)),
)
obj._value = values
+ if lenindef:
+ if v[:EOC_LEN].tobytes() != EOC:
+ raise DecodeError(
+ "no EOC",
+ klass=self.__class__,
+ decode_path=decode_path,
+ offset=offset,
+ )
+ tail = v[EOC_LEN:]
+ obj.lenindef = True
if not obj.ready:
raise DecodeError(
"not all values are ready",
decode_path=decode_path,
offset=offset,
)
- obj.lenindef = lenindef
- return obj, (v[EOC_LEN:] if lenindef else tail)
+ return obj, tail
class SequenceOf(Obj):
expl=self._expl,
default=self.default,
optional=self.optional,
- _decoded=(offset, llen, vlen),
+ _decoded=(offset, llen, vlen + (EOC_LEN if lenindef else 0)),
)
- obj.lenindef = lenindef
- return obj, (v[EOC_LEN:] if lenindef else tail)
+ if lenindef:
+ if v[:EOC_LEN].tobytes() != EOC:
+ raise DecodeError(
+ "no EOC",
+ klass=self.__class__,
+ decode_path=decode_path,
+ offset=offset,
+ )
+ obj.lenindef = True
+ tail = v[EOC_LEN:]
+ return obj, tail
def __repr__(self):
return "%s[%s]" % (
self.assertTrue(obj.bered)
self.assertFalse(obj.lenindef)
+ @given(
+ integers(min_value=1).map(tag_ctxc),
+ binary().filter(lambda x: not x.startswith(EOC)),
+ )
+ def test_ber_expl_no_eoc(self, expl, junk):
+ encoded = expl + b"\x80" + Boolean(False).encode()
+ with assertRaisesRegex(self, DecodeError, "no EOC"):
+ Boolean(expl=expl).decode(encoded + junk, ctx={"bered": True})
+ obj, tail = Boolean(expl=expl).decode(
+ encoded + EOC + junk,
+ ctx={"bered": True},
+ )
+ self.assertTrue(obj.expl_lenindef)
+ self.assertSequenceEqual(tail, junk)
+
@given(
integers(min_value=1).map(tag_ctxc),
lists(
max_size=3,
),
lists(booleans(), min_size=1),
+ binary(),
)
- def test_constructed(self, impl, chunk_inputs, chunk_last_bits):
+ def test_constructed(self, impl, chunk_inputs, chunk_last_bits, junk):
def chunk_constructed(contents):
return (
tag_encode(form=TagFormConstructed, num=3) +
(False, encoded_definite),
):
obj, tail = BitString(impl=tag_encode(impl)).decode(
- encoded, ctx={"bered": True}
+ encoded + junk,
+ ctx={"bered": True},
)
- self.assertSequenceEqual(tail, b"")
+ self.assertSequenceEqual(tail, junk)
self.assertEqual(obj.bit_len, bit_len_expected)
self.assertSequenceEqual(bytes(obj), payload_expected)
self.assertTrue(obj.bered)
self.assertEqual(obj.lenindef, lenindef_expected)
self.assertEqual(len(encoded), obj.tlvlen)
+ @given(
+ integers(min_value=0),
+ decode_path_strat,
+ )
+ def test_ber_definite_too_short(self, offset, decode_path):
+ with assertRaisesRegex(self, DecodeError, "longer than data") as err:
+ BitString().decode(
+ tag_encode(3, form=TagFormConstructed) + len_encode(1),
+ offset=offset,
+ decode_path=decode_path,
+ ctx={"bered": True},
+ )
+ self.assertEqual(err.exception.decode_path, decode_path)
+ self.assertEqual(err.exception.offset, offset)
+
+ @given(
+ integers(min_value=0),
+ decode_path_strat,
+ )
+ def test_ber_definite_no_data(self, offset, decode_path):
+ with assertRaisesRegex(self, DecodeError, "zero length") as err:
+ BitString().decode(
+ tag_encode(3, form=TagFormConstructed) + len_encode(0),
+ offset=offset,
+ decode_path=decode_path,
+ ctx={"bered": True},
+ )
+ self.assertEqual(err.exception.decode_path, decode_path)
+ self.assertEqual(err.exception.offset, offset)
+
+ @given(
+ integers(min_value=0),
+ decode_path_strat,
+ integers(min_value=1, max_value=3),
+ )
+ def test_ber_indefinite_no_eoc(self, offset, decode_path, chunks):
+ bs = BitString(b"data").encode()
+ with self.assertRaises(NotEnoughData) as err:
+ BitString().decode(
+ tag_encode(3, form=TagFormConstructed) + b"\x80" + chunks * bs,
+ offset=offset,
+ decode_path=decode_path,
+ ctx={"bered": True},
+ )
+ self.assertEqual(err.exception.decode_path, decode_path + (str(chunks),))
+ self.assertEqual(err.exception.offset, offset + 1 + 1 + chunks * len(bs))
+
+ @given(
+ integers(min_value=0),
+ decode_path_strat,
+ integers(min_value=1, max_value=3),
+ )
+ def test_ber_definite_chunk_out_of_bounds(self, offset, decode_path, chunks):
+ bs = BitString(b"data").encode()
+ bs_longer = BitString(b"data-longer").encode()
+ with assertRaisesRegex(self, DecodeError, "chunk out of bounds") as err:
+ BitString().decode(
+ (
+ tag_encode(3, form=TagFormConstructed) +
+ len_encode((chunks + 1) * len(bs)) +
+ chunks * bs +
+ bs_longer
+ ),
+ offset=offset,
+ decode_path=decode_path,
+ ctx={"bered": True},
+ )
+ self.assertEqual(err.exception.decode_path, decode_path + (str(chunks),))
+ self.assertEqual(err.exception.offset, offset + 1 + 1 + chunks * len(bs))
+
+ @given(
+ integers(min_value=0),
+ decode_path_strat,
+ )
+ def test_ber_indefinite_no_chunks(self, offset, decode_path):
+ with assertRaisesRegex(self, DecodeError, "no chunks") as err:
+ BitString().decode(
+ tag_encode(3, form=TagFormConstructed) + b"\x80" + EOC,
+ offset=offset,
+ decode_path=decode_path,
+ ctx={"bered": True},
+ )
+ self.assertEqual(err.exception.decode_path, decode_path)
+ self.assertEqual(err.exception.offset, offset)
+
+ @given(data_strategy())
+ def test_ber_indefinite_not_multiple(self, d):
+ bs_short = BitString("'A'H").encode()
+ bs_full = BitString("'AA'H").encode()
+ chunks = [bs_full for _ in range(d.draw(integers(min_value=0, max_value=3)))]
+ chunks.append(bs_short)
+ d.draw(permutations(chunks))
+ chunks.append(bs_short)
+ offset = d.draw(integers(min_value=0))
+ decode_path = d.draw(decode_path_strat)
+ with assertRaisesRegex(self, DecodeError, "multiple of 8 bits") as err:
+ BitString().decode(
+ (
+ tag_encode(3, form=TagFormConstructed) +
+ b"\x80" +
+ b"".join(chunks) +
+ EOC
+ ),
+ offset=offset,
+ decode_path=decode_path,
+ ctx={"bered": True},
+ )
+ self.assertEqual(
+ err.exception.decode_path,
+ decode_path + (str(chunks.index(bs_short)),),
+ )
+ self.assertEqual(
+ err.exception.offset,
+ offset + 1 + 1 + chunks.index(bs_short) * len(bs_full),
+ )
+
def test_x690_vector(self):
vector = BitString("'0A3B5F291CD'H")
obj, tail = BitString().decode(hexdec("0307040A3B5F291CD0"))
min_size=1,
max_size=3,
),
+ binary(),
)
- def test_constructed(self, impl, chunk_inputs):
+ def test_constructed(self, impl, chunk_inputs, junk):
def chunk_constructed(contents):
return (
tag_encode(form=TagFormConstructed, num=4) +
(False, encoded_definite),
):
obj, tail = OctetString(impl=tag_encode(impl)).decode(
- encoded, ctx={"bered": True}
+ encoded + junk,
+ ctx={"bered": True},
)
- self.assertSequenceEqual(tail, b"")
+ self.assertSequenceEqual(tail, junk)
self.assertSequenceEqual(bytes(obj), payload_expected)
self.assertTrue(obj.bered)
self.assertEqual(obj.lenindef, lenindef_expected)
self.assertEqual(len(encoded), obj.tlvlen)
+ @given(
+ integers(min_value=0),
+ decode_path_strat,
+ )
+ def test_ber_definite_too_short(self, offset, decode_path):
+ with assertRaisesRegex(self, DecodeError, "longer than data") as err:
+ OctetString().decode(
+ tag_encode(4, form=TagFormConstructed) + len_encode(1),
+ offset=offset,
+ decode_path=decode_path,
+ ctx={"bered": True},
+ )
+ self.assertEqual(err.exception.decode_path, decode_path)
+ self.assertEqual(err.exception.offset, offset)
+
+ @given(
+ integers(min_value=0),
+ decode_path_strat,
+ integers(min_value=1, max_value=3),
+ )
+ def test_ber_indefinite_no_eoc(self, offset, decode_path, chunks):
+ bs = OctetString(b"data").encode()
+ with self.assertRaises(NotEnoughData) as err:
+ OctetString().decode(
+ tag_encode(4, form=TagFormConstructed) + b"\x80" + chunks * bs,
+ offset=offset,
+ decode_path=decode_path,
+ ctx={"bered": True},
+ )
+ self.assertEqual(err.exception.decode_path, decode_path + (str(chunks),))
+ self.assertEqual(err.exception.offset, offset + 1 + 1 + chunks * len(bs))
+
+ @given(
+ integers(min_value=0),
+ decode_path_strat,
+ integers(min_value=1, max_value=3),
+ )
+ def test_ber_definite_chunk_out_of_bounds(self, offset, decode_path, chunks):
+ bs = OctetString(b"data").encode()
+ bs_longer = OctetString(b"data-longer").encode()
+ with assertRaisesRegex(self, DecodeError, "chunk out of bounds") as err:
+ OctetString().decode(
+ (
+ tag_encode(4, form=TagFormConstructed) +
+ len_encode((chunks + 1) * len(bs)) +
+ chunks * bs +
+ bs_longer
+ ),
+ offset=offset,
+ decode_path=decode_path,
+ ctx={"bered": True},
+ )
+ self.assertEqual(err.exception.decode_path, decode_path + (str(chunks),))
+ self.assertEqual(err.exception.offset, offset + 1 + 1 + chunks * len(bs))
+
@composite
def null_values_strategy(draw, do_expl=False):
self.assertEqual(obj_decoded.llen, 0)
self.assertEqual(obj_decoded.vlen, len(value))
+ @given(
+ integers(min_value=1).map(tag_ctxc),
+ integers(min_value=0, max_value=3),
+ integers(min_value=0),
+ decode_path_strat,
+ binary(),
+ )
+ def test_indefinite(self, expl, chunks, offset, decode_path, junk):
+ chunk = Boolean(False, expl=expl).encode()
+ encoded = (
+ OctetString.tag_default +
+ b"\x80" +
+ b"".join([chunk] * chunks) +
+ EOC
+ )
+ obj, tail = Any().decode(
+ encoded + junk,
+ offset=offset,
+ decode_path=decode_path,
+ ctx={"bered": True},
+ )
+ self.assertSequenceEqual(tail, junk)
+ self.assertEqual(obj.offset, offset)
+ self.assertEqual(obj.tlvlen, len(encoded))
+ with self.assertRaises(NotEnoughData) as err:
+ Any().decode(
+ encoded[:-1],
+ offset=offset,
+ decode_path=decode_path,
+ ctx={"bered": True},
+ )
+ self.assertEqual(err.exception.offset, offset + 1 + 1 + len(chunk) * chunks)
+ self.assertEqual(err.exception.decode_path, decode_path + (str(chunks),))
+
@composite
def choice_values_strategy(draw, value_required=False, schema=None, do_expl=False):
self._assert_expects(seq, expects)
repr(seq)
pprint(seq)
+ self.assertTrue(seq.ready)
seq_encoded = seq.encode()
seq_decoded, tail = seq.decode(seq_encoded + tail_junk)
- self.assertEqual(tail, tail_junk)
- self.assertTrue(seq.ready)
- self._assert_expects(seq_decoded, expects)
- self.assertEqual(seq, seq_decoded)
- self.assertEqual(seq_decoded.encode(), seq_encoded)
- for expect in expects:
- if not expect["presented"]:
- self.assertNotIn(expect["name"], seq_decoded)
- continue
- self.assertIn(expect["name"], seq_decoded)
- obj = seq_decoded[expect["name"]]
- self.assertTrue(obj.decoded)
- offset = obj.expl_offset if obj.expled else obj.offset
- tlvlen = obj.expl_tlvlen if obj.expled else obj.tlvlen
- self.assertSequenceEqual(
- seq_encoded[offset:offset + tlvlen],
- obj.encode(),
- )
+ self.assertFalse(seq_decoded.lenindef)
+
+ t, _, lv = tag_strip(seq_encoded)
+ _, _, v = len_decode(lv)
+ seq_encoded_lenindef = t + b"\x80" + v + EOC
+ seq_decoded_lenindef, tail_lenindef = seq.decode(
+ seq_encoded_lenindef + tail_junk,
+ ctx={"bered": True},
+ )
+ self.assertTrue(seq_decoded_lenindef.lenindef)
+ with self.assertRaises(DecodeError):
+ seq.decode(seq_encoded_lenindef[:-1], ctx={"bered": True})
+ with self.assertRaises(DecodeError):
+ seq.decode(seq_encoded_lenindef[:-2], ctx={"bered": True})
+ repr(seq_decoded_lenindef)
+ pprint(seq_decoded_lenindef)
+ self.assertTrue(seq_decoded_lenindef.ready)
+
+ for decoded, decoded_tail, encoded in (
+ (seq_decoded, tail, seq_encoded),
+ (seq_decoded_lenindef, tail_lenindef, seq_encoded_lenindef),
+ ):
+ self.assertEqual(decoded_tail, tail_junk)
+ self._assert_expects(decoded, expects)
+ self.assertEqual(seq, decoded)
+ self.assertEqual(decoded.encode(), seq_encoded)
+ self.assertEqual(decoded.tlvlen, len(encoded))
+ for expect in expects:
+ if not expect["presented"]:
+ self.assertNotIn(expect["name"], decoded)
+ continue
+ self.assertIn(expect["name"], decoded)
+ obj = decoded[expect["name"]]
+ self.assertTrue(obj.decoded)
+ offset = obj.expl_offset if obj.expled else obj.offset
+ tlvlen = obj.expl_tlvlen if obj.expled else obj.tlvlen
+ self.assertSequenceEqual(
+ seq_encoded[offset:offset + tlvlen],
+ obj.encode(),
+ )
@settings(max_examples=LONG_TEST_MAX_EXAMPLES)
@given(data_strategy())
],
)
+ t, _, lv = tag_strip(obj_encoded)
+ _, _, v = len_decode(lv)
+ obj_encoded_lenindef = t + b"\x80" + v + EOC
+ obj_decoded_lenindef, tail_lenindef = obj.decode(
+ obj_encoded_lenindef + tail_junk,
+ ctx={"bered": True},
+ )
+ self.assertTrue(obj_decoded_lenindef.lenindef)
+ repr(obj_decoded_lenindef)
+ pprint(obj_decoded_lenindef)
+ self.assertEqual(obj_decoded_lenindef.tlvlen, len(obj_encoded_lenindef))
+ with self.assertRaises(DecodeError):
+ obj.decode(obj_encoded_lenindef[:-1], ctx={"bered": True})
+ with self.assertRaises(DecodeError):
+ obj.decode(obj_encoded_lenindef[:-2], ctx={"bered": True})
+
class TestSequenceOf(SeqOfMixing, CommonMixin, TestCase):
class SeqOf(SequenceOf):