]> Cypherpunks.ru repositories - pyderasn.git/blobdiff - pyderasn.py
Strict SET OF values ordering check
[pyderasn.git] / pyderasn.py
index 3b7d6cc75dcd5398cd5d440ee742fa26c9a4986c..df639eb9ffef8ece9ce95660464b91bbddb8b285 100755 (executable)
@@ -213,9 +213,11 @@ decoding process.
 
 Currently available context options:
 
+* :ref:`allow_default_values <allow_default_values_ctx>`
+* :ref:`allow_expl_oob <allow_expl_oob_ctx>`
+* :ref:`allow_unordered_set <allow_unordered_set_ctx>`
 * :ref:`bered <bered_ctx>`
 * :ref:`defines_by_path <defines_by_path_ctx>`
-* :ref:`strict_default_existence <strict_default_existence_ctx>`
 
 .. _pprinting:
 
@@ -382,7 +384,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
@@ -393,6 +395,22 @@ constructed primitive types should be parsed successfully.
 EOC (end-of-contents) token's length is taken in advance in object's
 value length.
 
+.. _allow_expl_oob_ctx:
+
+Allow explicit tag out-of-bound
+-------------------------------
+
+Invalid BER encoding could contain ``EXPLICIT`` tag containing more than
+one value, more than one object. If you set ``allow_expl_oob`` context
+option to True, then no error will be raised and that invalid encoding
+will be silently further processed. But pay attention that offsets and
+lengths will be invalid in that case.
+
+.. warning::
+
+   This option should be used only for skipping some decode errors, just
+   to see the decoded structure somehow.
+
 Primitive types
 ---------------
 
@@ -1092,6 +1110,13 @@ class Obj(object):
                 if tag_only:
                     return
                 obj, tail = result
+                if obj.tlvlen < l and not ctx.get("allow_expl_oob", False):
+                    raise DecodeError(
+                        "explicit tag out-of-bound, longer than data",
+                        klass=self.__class__,
+                        decode_path=decode_path,
+                        offset=offset,
+                    )
         return obj, (tail if leavemm else tail.tobytes())
 
     @property
@@ -4314,18 +4339,14 @@ class Sequence(Obj):
 
     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 <ctx>` 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 <bered_ctx>` 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 <ctx>` option.
 
     Two sequences are equal if they have equal specification (schema),
     implicit/explicit tagging and the same values.
@@ -4485,10 +4506,11 @@ class Sequence(Obj):
         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__,
@@ -4516,6 +4538,8 @@ class Sequence(Obj):
         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
@@ -4588,15 +4612,15 @@ class Sequence(Obj):
             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", ())
@@ -4639,6 +4663,7 @@ class Sequence(Obj):
         )
         obj._value = values
         obj.lenindef = lenindef
+        obj.bered = bered
         return obj, tail
 
     def __repr__(self):
@@ -4684,6 +4709,14 @@ class Set(Sequence):
     """``SET`` structure type
 
     Its usage is identical to :py:class:`pyderasn.Sequence`.
+
+    .. _allow_unordered_set_ctx:
+
+    DER prohibits unordered values encoding and will raise an error
+    during decode. If If :ref:`bered <bered_ctx>` context option is set,
+    then no error will occure. Also you can disable strict values
+    ordering check by setting ``"allow_unordered_set": True``
+    :ref:`context <ctx>` option.
     """
     __slots__ = ()
     tag_default = tag_encode(form=TagFormConstructed, num=17)
@@ -4714,10 +4747,11 @@ class Set(Sequence):
         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__,
@@ -4744,6 +4778,10 @@ class Set(Sequence):
         vlen = 0
         sub_offset = offset + tlen + llen
         values = {}
+        bered = False
+        ctx_allow_default_values = ctx.get("allow_default_values", False)
+        ctx_allow_unordered_set = ctx.get("allow_unordered_set", False)
+        value_prev = memoryview(v[:0])
         specs_items = self.specs.items
         while len(v) > 0:
             if lenindef and v[:EOC_LEN].tobytes() == EOC:
@@ -4776,12 +4814,32 @@ class Set(Sequence):
                 ctx=ctx,
             )
             value_len = value.fulllen
+            if value_prev.tobytes() > v[:value_len].tobytes():
+                if ctx_bered or ctx_allow_unordered_set:
+                    bered = True
+                else:
+                    raise DecodeError(
+                        "unordered " + self.asn1_type_name,
+                        klass=self.__class__,
+                        decode_path=sub_decode_path,
+                        offset=sub_offset,
+                    )
+            if spec.default is None or value != spec.default:
+                pass
+            elif 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,
+                )
+            values[name] = value
+            value_prev = v[:value_len]
             sub_offset += value_len
             vlen += value_len
             v = v_tail
-            if spec.default is None or value != spec.default:  # pragma: no cover
-                # SeqMixing.test_encoded_default_accepted covers that place
-                values[name] = value
         obj = self.__class__(
             schema=self.specs,
             impl=self.tag,
@@ -4790,7 +4848,6 @@ class Set(Sequence):
             optional=self.optional,
             _decoded=(offset, llen, vlen + (EOC_LEN if lenindef else 0)),
         )
-        obj._value = values
         if lenindef:
             if v[:EOC_LEN].tobytes() != EOC:
                 raise DecodeError(
@@ -4801,6 +4858,7 @@ class Set(Sequence):
                 )
             tail = v[EOC_LEN:]
             obj.lenindef = True
+        obj._value = values
         if not obj.ready:
             raise DecodeError(
                 "not all values are ready",
@@ -4808,6 +4866,7 @@ class Set(Sequence):
                 decode_path=decode_path,
                 offset=offset,
             )
+        obj.bered = bered
         return obj, tail
 
 
@@ -4991,7 +5050,7 @@ class SequenceOf(Obj):
         v = b"".join(self._encoded_values())
         return b"".join((self.tag, len_encode(len(v)), v))
 
-    def _decode(self, tlv, offset, decode_path, ctx, tag_only):
+    def _decode(self, tlv, offset, decode_path, ctx, tag_only, ordering_check=False):
         try:
             t, tlen, lv = tag_strip(tlv)
         except DecodeError as err:
@@ -5010,10 +5069,11 @@ class SequenceOf(Obj):
         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__,
@@ -5041,22 +5101,38 @@ class SequenceOf(Obj):
         vlen = 0
         sub_offset = offset + tlen + llen
         _value = []
+        ctx_allow_unordered_set = ctx.get("allow_unordered_set", False)
+        value_prev = memoryview(v[:0])
+        bered = False
         spec = self.spec
         while len(v) > 0:
             if lenindef and v[:EOC_LEN].tobytes() == EOC:
                 break
+            sub_decode_path = decode_path + (str(len(_value)),)
             value, v_tail = spec.decode(
                 v,
                 sub_offset,
                 leavemm=True,
-                decode_path=decode_path + (str(len(_value)),),
+                decode_path=sub_decode_path,
                 ctx=ctx,
             )
             value_len = value.fulllen
+            if ordering_check:
+                if value_prev.tobytes() > v[:value_len].tobytes():
+                    if ctx_bered or ctx_allow_unordered_set:
+                        bered = True
+                    else:
+                        raise DecodeError(
+                            "unordered " + self.asn1_type_name,
+                            klass=self.__class__,
+                            decode_path=sub_decode_path,
+                            offset=sub_offset,
+                        )
+                value_prev = v[:value_len]
+            _value.append(value)
             sub_offset += value_len
             vlen += value_len
             v = v_tail
-            _value.append(value)
         try:
             obj = self.__class__(
                 value=_value,
@@ -5085,6 +5161,7 @@ class SequenceOf(Obj):
                 )
             obj.lenindef = True
             tail = v[EOC_LEN:]
+        obj.bered = bered
         return obj, tail
 
     def __repr__(self):
@@ -5134,6 +5211,16 @@ class SetOf(SequenceOf):
         v = b"".join(raws)
         return b"".join((self.tag, len_encode(len(v)), v))
 
+    def _decode(self, tlv, offset, decode_path, ctx, tag_only):
+        return super(SetOf, self)._decode(
+            tlv,
+            offset,
+            decode_path,
+            ctx,
+            tag_only,
+            ordering_check=True,
+        )
+
 
 def obj_by_path(pypath):  # pragma: no cover
     """Import object specified as string Python path
@@ -5234,18 +5321,23 @@ def main():  # pragma: no cover
     )
     parser.add_argument(
         "--nobered",
-        action='store_true',
+        action="store_true",
         help="Disallow BER encoding",
     )
     parser.add_argument(
         "--print-decode-path",
-        action='store_true',
+        action="store_true",
         help="Print decode paths",
     )
     parser.add_argument(
         "--decode-path-only",
         help="Print only specified decode path",
     )
+    parser.add_argument(
+        "--allow-expl-oob",
+        action="store_true",
+        help="Allow explicit tag out-of-bound",
+    )
     parser.add_argument(
         "DERFile",
         type=argparse.FileType("rb"),
@@ -5262,7 +5354,10 @@ def main():  # pragma: no cover
         pprinter = partial(pprint, big_blobs=True)
     else:
         schema, pprinter = generic_decoder()
-    ctx = {"bered": not args.nobered}
+    ctx = {
+        "bered": not args.nobered,
+        "allow_expl_oob": args.allow_expl_oob,
+    }
     if args.defines_by_path is not None:
         ctx["defines_by_path"] = obj_by_path(args.defines_by_path)
     obj, tail = schema().decode(der, ctx=ctx)