VERSION | 2 +- doc/news.rst | 11 +++++++---- pyderasn.py | 62 ++++++++++++++++++++++++++++------------------------- tests/test_pyderasn.py | 22 +++++++++++++++------- diff --git a/VERSION b/VERSION index 65c7b35a29855a225e3fea4944db9bbcbd5db46b824ad2888a44b6112d8c57c1..dddd2b6fe8e64351ebd48d13898cd77b3dd21803c72f8a028b265ed6c316055f 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -3.15 +4.0 diff --git a/doc/news.rst b/doc/news.rst index c6bc37d0218b226db2ccff55cf7e10da45d070e211af8f2cef2140c2d821b8a4..3c4d04452a02c281558556fef70e3d8c7389e4eb4f168c43bcefaab8a94b1953 100644 --- a/doc/news.rst +++ b/doc/news.rst @@ -1,11 +1,14 @@ News ==== -.. _release3.15: +.. _release4.0: -3.15 ----- -* DEFAULT-encoded value is checked also for Set-s, not for Sequences only +4.0 +--- +* Default value is checked also for Sets, not for Sequences only +* **Incompatible** change: defaulted values in Sequence/Set are always + strictly checked, unless ``allow_default_values`` context option is + set. ``strict_default_existence`` option disappeared .. _release3.14: diff --git a/pyderasn.py b/pyderasn.py index 54c9983489d64c4f2fe68292d2f5ed0a5d9c9e150d4779053ab475fc0e268d05..0ed59a30a12acc345d9f8edee69d58888caaa23848781ccd4b4598fa169119e1 100755 --- a/pyderasn.py +++ b/pyderasn.py @@ -213,10 +213,10 @@ decoding process. Currently available context options: +* :ref:`allow_default_values ` * :ref:`allow_expl_oob ` * :ref:`bered ` * :ref:`defines_by_path ` -* :ref:`strict_default_existence ` .. _pprinting: @@ -383,7 +383,7 @@ constructed primitive types should be parsed successfully. * If object is encoded in BER form (not the DER one), then ``bered`` attribute is set to True. Only ``BOOLEAN``, ``BIT STRING``, ``OCTET - STRING`` can contain it. + STRING``, ``SEQUENCE``, ``SET`` can contain it. * If object has an indefinite length encoding, then its ``lenindef`` attribute is set to True. Only ``BIT STRING``, ``OCTET STRING``, ``SEQUENCE``, ``SET``, ``SEQUENCE OF``, ``SET OF``, ``ANY`` can @@ -4338,18 +4338,14 @@ (True, BOOLEAN True) All defaulted values are always optional. - .. _strict_default_existence_ctx: + .. _allow_default_values_ctx: - .. warning:: - - When decoded DER contains defaulted value inside, then - technically this is not valid DER encoding. But we allow and pass - it **by default**. Of course reencoding of that kind of DER will - result in different binary representation (validly without - defaulted value inside). You can enable strict defaulted values - existence validation by setting ``"strict_default_existence": - True`` :ref:`context ` option -- decoding process will raise - an exception if defaulted value is met. + DER prohibits default value encoding and will raise an error if + default value is unexpectedly met during decode. + If :ref:`bered ` context option is set, then no error + will be raised, but ``bered`` attribute set. You can disable strict + defaulted values existence validation by setting + ``"allow_default_values": True`` :ref:`context ` option. Two sequences are equal if they have equal specification (schema), implicit/explicit tagging and the same values. @@ -4509,10 +4505,11 @@ ) if tag_only: return lenindef = False + ctx_bered = ctx.get("bered", False) try: l, llen, v = len_decode(lv) except LenIndefForm as err: - if not ctx.get("bered", False): + if not ctx_bered: raise err.__class__( msg=err.msg, klass=self.__class__, @@ -4540,6 +4537,8 @@ v, tail = v[:l], v[l:] vlen = 0 sub_offset = offset + tlen + llen values = {} + bered = False + ctx_allow_default_values = ctx.get("allow_default_values", False) for name, spec in self.specs.items(): if spec.optional and ( (lenindef and v[:EOC_LEN].tobytes() == EOC) or @@ -4612,15 +4611,15 @@ vlen += value_len sub_offset += value_len v = v_tail if spec.default is not None and value == spec.default: - if ctx.get("strict_default_existence", False): + if ctx_bered or ctx_allow_default_values: + bered = True + else: raise DecodeError( "DEFAULT value met", klass=self.__class__, decode_path=sub_decode_path, offset=sub_offset, ) - else: - continue values[name] = value spec_defines = getattr(spec, "defines", ()) @@ -4663,6 +4662,7 @@ _decoded=(offset, llen, vlen), ) obj._value = values obj.lenindef = lenindef + obj.bered = bered return obj, tail def __repr__(self): @@ -4738,10 +4738,11 @@ ) if tag_only: return lenindef = False + ctx_bered = ctx.get("bered", False) try: l, llen, v = len_decode(lv) except LenIndefForm as err: - if not ctx.get("bered", False): + if not ctx_bered: raise err.__class__( msg=err.msg, klass=self.__class__, @@ -4768,6 +4769,8 @@ v, tail = v[:l], v[l:] vlen = 0 sub_offset = offset + tlen + llen values = {} + bered = False + ctx_allow_default_values = ctx.get("allow_default_values", False) specs_items = self.specs.items while len(v) > 0: if lenindef and v[:EOC_LEN].tobytes() == EOC: @@ -4803,18 +4806,18 @@ value_len = value.fulllen sub_offset += value_len vlen += value_len v = v_tail - if spec.default is None: - values[name] = value + if spec.default is None or value != spec.default: + pass + elif ctx_bered or ctx_allow_default_values: + bered = True else: - if value != spec.default: - values[name] = value - if ctx.get("strict_default_existence", False): - raise DecodeError( - "DEFAULT value met", - klass=self.__class__, - decode_path=sub_decode_path, - offset=sub_offset, - ) + raise DecodeError( + "DEFAULT value met", + klass=self.__class__, + decode_path=sub_decode_path, + offset=sub_offset, + ) + values[name] = value obj = self.__class__( schema=self.specs, impl=self.tag, @@ -4824,6 +4827,7 @@ optional=self.optional, _decoded=(offset, llen, vlen + (EOC_LEN if lenindef else 0)), ) obj._value = values + obj.bered = bered if lenindef: if v[:EOC_LEN].tobytes() != EOC: raise DecodeError( diff --git a/tests/test_pyderasn.py b/tests/test_pyderasn.py index f125e3bf94160c79fa44e84bad3bc9f20057d353bae027d741bc4c079efc3f5a..662c2c4ec041ac87e07fc1ee299737a588dfc5a6939730b6195f43c5da7a4454 100644 --- a/tests/test_pyderasn.py +++ b/tests/test_pyderasn.py @@ -4749,6 +4749,7 @@ self.assertTrue(seq.ready) seq_encoded = seq.encode() seq_decoded, tail = seq.decode(seq_encoded + tail_junk) self.assertFalse(seq_decoded.lenindef) + self.assertFalse(seq_decoded.bered) t, _, lv = tag_strip(seq_encoded) _, _, v = len_decode(lv) @@ -4840,7 +4841,7 @@ self.assertEqual(len(seq._value), 0) self.assertSequenceEqual(seq.encode(), empty_seq) @given(data_strategy()) - def test_encoded_default_accepted(self, d): + def test_encoded_default_not_accepted(self, d): _schema = list(d.draw(dictionaries( text_letters(), integers(), @@ -4868,10 +4869,14 @@ (n, Integer(default=v, impl=t)) for (n, v), t in zip(_schema, tags) ] seq_with_default = SeqWithDefault() - seq_decoded, _ = seq_with_default.decode(seq_encoded) - for name, value in _schema: - self.assertEqual(seq_decoded[name], seq_with_default[name]) - self.assertEqual(seq_decoded[name], value) + with assertRaisesRegex(self, DecodeError, "DEFAULT value met"): + seq_with_default.decode(seq_encoded) + for ctx in ({"bered": True}, {"allow_default_values": True}): + seq_decoded, _ = seq_with_default.decode(seq_encoded, ctx=ctx) + self.assertTrue(seq_decoded.bered) + for name, value in _schema: + self.assertEqual(seq_decoded[name], seq_with_default[name]) + self.assertEqual(seq_decoded[name], value) @given(data_strategy()) def test_missing_from_spec(self, d): @@ -5880,9 +5885,12 @@ seq["int%d" % i] = Integer(123) raw = seq.encode() chosen_choice = "int%d" % chosen seq.specs[chosen_choice] = seq.specs[chosen_choice](default=123) - seq.decode(raw) with assertRaisesRegex(self, DecodeError, "DEFAULT value met"): - seq.decode(raw, ctx={"strict_default_existence": True}) + seq.decode(raw) + decoded, _ = seq.decode(raw, ctx={"allow_default_values": True}) + self.assertTrue(decoded.bered) + decoded, _ = seq.decode(raw, ctx={"bered": True}) + self.assertTrue(decoded.bered) class TestX690PrefixedType(TestCase):