]> Cypherpunks.ru repositories - pyderasn.git/blobdiff - pyderasn.py
Strict SET OF values ordering check
[pyderasn.git] / pyderasn.py
index f7e6c4c53f9cb5d5316f555ca42658b0301d4308..df639eb9ffef8ece9ce95660464b91bbddb8b285 100755 (executable)
@@ -189,9 +189,16 @@ use following properties:
 
 Pay attention that those values do **not** include anything related to
 explicit tag. If you want to know information about it, then use:
-``expled`` (to know if explicit tag is set), ``expl_offset`` (it is
-lesser than ``offset``), ``expl_tlen``, ``expl_llen``, ``expl_vlen``
-(that actually equals to ordinary ``tlvlen``).
+
+* ``expled`` -- to know if explicit tag is set
+* ``expl_offset`` (it is lesser than ``offset``)
+* ``expl_tlen``,
+* ``expl_llen``
+* ``expl_vlen`` (that actually equals to ordinary ``tlvlen``)
+* ``fulloffset`` -- it equals to ``expl_offset`` if explicit tag is set,
+  ``offset`` otherwise
+* ``fulllen`` -- it equals to ``expl_len`` if explicit tag is set,
+  ``tlvlen`` otherwise
 
 When error occurs, :py:exc:`pyderasn.DecodeError` is raised.
 
@@ -206,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:
 
@@ -267,7 +276,7 @@ You can specify multiple fields, that will be autodecoded -- that is why
 ``defines`` kwarg is a sequence. You can specify defined field
 relatively or absolutely to current decode path. For example ``defines``
 for AlgorithmIdentifier of X.509's
-``tbsCertificate.subjectPublicKeyInfo.algorithm.algorithm``::
+``tbsCertificate:subjectPublicKeyInfo:algorithm:algorithm``::
 
         (
             (("parameters",), {
@@ -295,8 +304,7 @@ Following types can be automatically decoded (DEFINED BY):
 When any of those fields is automatically decoded, then ``.defined``
 attribute contains ``(OID, value)`` tuple. ``OID`` tells by which OID it
 was defined, ``value`` contains corresponding decoded value. For example
-above, ``content_info["content"].defined == (id_signedData,
-signed_data)``.
+above, ``content_info["content"].defined == (id_signedData, signed_data)``.
 
 .. _defines_by_path_ctx:
 
@@ -369,10 +377,6 @@ useful for SEQUENCE/SET OF-s.
 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
@@ -380,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
@@ -391,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
 ---------------
 
@@ -432,6 +452,10 @@ CommonString
 ____________
 .. autoclass:: pyderasn.CommonString
 
+NumericString
+_____________
+.. autoclass:: pyderasn.NumericString
+
 UTCTime
 _______
 .. autoclass:: pyderasn.UTCTime
@@ -492,6 +516,17 @@ Various
 .. autofunction:: pyderasn.tag_ctxp
 .. autofunction:: pyderasn.tag_ctxc
 .. autoclass:: pyderasn.Obj
+.. autoclass:: pyderasn.DecodeError
+   :members: __init__
+.. autoclass:: pyderasn.NotEnoughData
+.. autoclass:: pyderasn.LenIndefForm
+.. autoclass:: pyderasn.TagMismatch
+.. autoclass:: pyderasn.InvalidLength
+.. autoclass:: pyderasn.InvalidOID
+.. autoclass:: pyderasn.ObjUnknown
+.. autoclass:: pyderasn.ObjNotReady
+.. autoclass:: pyderasn.InvalidValueType
+.. autoclass:: pyderasn.BoundsError
 """
 
 from codecs import getdecoder
@@ -544,6 +579,7 @@ __all__ = (
     "InvalidOID",
     "InvalidValueType",
     "ISO646String",
+    "LenIndefForm",
     "NotEnoughData",
     "Null",
     "NumericString",
@@ -591,6 +627,8 @@ TagClassReprs = {
 }
 EOC = b"\x00\x00"
 EOC_LEN = len(EOC)
+LENINDEF = b"\x80"  # length indefinite mark
+LENINDEF_PP_CHAR = "I" if PY2 else "∞"
 
 
 ########################################################################
@@ -620,7 +658,7 @@ class DecodeError(Exception):
             c for c in (
                 "" if self.klass is None else self.klass.__name__,
                 (
-                    ("(%s)" % ".".join(str(dp) for dp in self.decode_path))
+                    ("(%s)" % ":".join(str(dp) for dp in self.decode_path))
                     if len(self.decode_path) > 0 else ""
                 ),
                 ("(at %d)" % self.offset) if self.offset > 0 else "",
@@ -898,9 +936,7 @@ class Obj(object):
         self.tag = getattr(self, "impl", self.tag_default) if impl is None else impl
         self._expl = getattr(self, "expl", None) if expl is None else expl
         if self.tag != self.tag_default and self._expl is not None:
-            raise ValueError(
-                "implicit and explicit tags can not be set simultaneously"
-            )
+            raise ValueError("implicit and explicit tags can not be set simultaneously")
         if default is not None:
             optional = True
         self.optional = optional
@@ -1042,7 +1078,8 @@ class Obj(object):
                 eoc_expected, tail = tail[:EOC_LEN], tail[EOC_LEN:]
                 if eoc_expected.tobytes() != EOC:
                     raise DecodeError(
-                        msg="no EOC",
+                        "no EOC",
+                        klass=self.__class__,
                         decode_path=decode_path,
                         offset=offset,
                     )
@@ -1073,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
@@ -1105,6 +1149,41 @@ class Obj(object):
     def expl_tlvlen(self):
         return self.expl_tlen + self.expl_llen + self.expl_vlen
 
+    @property
+    def fulloffset(self):
+        return self.expl_offset if self.expled else self.offset
+
+    @property
+    def fulllen(self):
+        return self.expl_tlvlen if self.expled else self.tlvlen
+
+    def pps_lenindef(self, decode_path):
+        if self.lenindef:
+            yield _pp(
+                asn1_type_name="EOC",
+                obj_name="",
+                decode_path=decode_path,
+                offset=(
+                    self.offset + self.tlvlen -
+                    (EOC_LEN * 2 if self.expl_lenindef else EOC_LEN)
+                ),
+                tlen=1,
+                llen=1,
+                vlen=0,
+                bered=True,
+            )
+        if self.expl_lenindef:
+            yield _pp(
+                asn1_type_name="EOC",
+                obj_name="EXPLICIT",
+                decode_path=decode_path,
+                offset=self.expl_offset + self.expl_tlvlen - EOC_LEN,
+                tlen=1,
+                llen=1,
+                vlen=0,
+                bered=True,
+            )
+
 
 class DecodePathDefBy(object):
     """DEFINED BY representation inside decode path
@@ -1203,7 +1282,7 @@ def _pp(
     )
 
 
-def _colorize(what, colour, with_colours, attrs=("bold",)):
+def _colourize(what, colour, with_colours, attrs=("bold",)):
     return colored(what, colour, attrs=attrs) if with_colours else what
 
 
@@ -1213,34 +1292,34 @@ def pp_console_row(
         with_offsets=False,
         with_blob=True,
         with_colours=False,
+        with_decode_path=False,
+        decode_path_len_decrease=0,
 ):
     cols = []
     if with_offsets:
-        col = "%5d%s" % (
+        col = "%5d%s%s" % (
             pp.offset,
             (
                 "  " if pp.expl_offset is None else
                 ("-%d" % (pp.offset - pp.expl_offset))
             ),
+            LENINDEF_PP_CHAR if pp.expl_lenindef else " ",
         )
-        cols.append(_colorize(col, "red", with_colours, ()))
-        col = "[%d,%d,%4d]" % (pp.tlen, pp.llen, pp.vlen)
-        col = _colorize(col, "green", with_colours, ())
-        ber_deoffset = 0
-        if pp.expl_lenindef:
-            ber_deoffset += 2
-        if pp.lenindef:
-            ber_deoffset += 2
-        col += (
-            "  " if ber_deoffset == 0 else
-            _colorize(("-%d" % ber_deoffset), "red", with_colours)
+        cols.append(_colourize(col, "red", with_colours, ()))
+        col = "[%d,%d,%4d]%s" % (
+            pp.tlen,
+            pp.llen,
+            pp.vlen,
+            LENINDEF_PP_CHAR if pp.lenindef else " "
         )
+        col = _colourize(col, "green", with_colours, ())
         cols.append(col)
-    if len(pp.decode_path) > 0:
-        cols.append(" ." * (len(pp.decode_path)))
+    decode_path_len = len(pp.decode_path) - decode_path_len_decrease
+    if decode_path_len > 0:
+        cols.append(" ." * decode_path_len)
         ent = pp.decode_path[-1]
         if isinstance(ent, DecodePathDefBy):
-            cols.append(_colorize("DEFINED BY", "red", with_colours, ("reverse",)))
+            cols.append(_colourize("DEFINED BY", "red", with_colours, ("reverse",)))
             value = str(ent.defined_by)
             if (
                     oids is not None and
@@ -1248,49 +1327,56 @@ def pp_console_row(
                     ObjectIdentifier.asn1_type_name and
                     value in oids
             ):
-                cols.append(_colorize("%s:" % oids[value], "green", with_colours))
+                cols.append(_colourize("%s:" % oids[value], "green", with_colours))
             else:
-                cols.append(_colorize("%s:" % value, "white", with_colours, ("reverse",)))
+                cols.append(_colourize("%s:" % value, "white", with_colours, ("reverse",)))
         else:
-            cols.append(_colorize("%s:" % ent, "yellow", with_colours, ("reverse",)))
+            cols.append(_colourize("%s:" % ent, "yellow", with_colours, ("reverse",)))
     if pp.expl is not None:
         klass, _, num = pp.expl
         col = "[%s%d] EXPLICIT" % (TagClassReprs[klass], num)
-        cols.append(_colorize(col, "blue", with_colours))
+        cols.append(_colourize(col, "blue", with_colours))
     if pp.impl is not None:
         klass, _, num = pp.impl
         col = "[%s%d]" % (TagClassReprs[klass], num)
-        cols.append(_colorize(col, "blue", with_colours))
+        cols.append(_colourize(col, "blue", with_colours))
     if pp.asn1_type_name.replace(" ", "") != pp.obj_name.upper():
-        cols.append(_colorize(pp.obj_name, "magenta", with_colours))
+        cols.append(_colourize(pp.obj_name, "magenta", with_colours))
     if pp.bered:
-        cols.append(_colorize("BER", "red", with_colours))
-    cols.append(_colorize(pp.asn1_type_name, "cyan", with_colours))
+        cols.append(_colourize("BER", "red", with_colours))
+    cols.append(_colourize(pp.asn1_type_name, "cyan", with_colours))
     if pp.value is not None:
         value = pp.value
-        cols.append(_colorize(value, "white", with_colours, ("reverse",)))
+        cols.append(_colourize(value, "white", with_colours, ("reverse",)))
         if (
                 oids is not None and
                 pp.asn1_type_name == ObjectIdentifier.asn1_type_name and
                 value in oids
         ):
-            cols.append(_colorize("(%s)" % oids[value], "green", with_colours))
+            cols.append(_colourize("(%s)" % oids[value], "green", with_colours))
     if with_blob:
         if isinstance(pp.blob, binary_type):
             cols.append(hexenc(pp.blob))
         elif isinstance(pp.blob, tuple):
             cols.append(", ".join(pp.blob))
     if pp.optional:
-        cols.append(_colorize("OPTIONAL", "red", with_colours))
+        cols.append(_colourize("OPTIONAL", "red", with_colours))
     if pp.default:
-        cols.append(_colorize("DEFAULT", "red", with_colours))
+        cols.append(_colourize("DEFAULT", "red", with_colours))
+    if with_decode_path:
+        cols.append(_colourize(
+            "[%s]" % ":".join(str(p) for p in pp.decode_path),
+            "grey",
+            with_colours,
+        ))
     return " ".join(cols)
 
 
-def pp_console_blob(pp):
-    cols = [" " * len("XXXXXYY [X,X,XXXX]YY")]
-    if len(pp.decode_path) > 0:
-        cols.append(" ." * (len(pp.decode_path) + 1))
+def pp_console_blob(pp, decode_path_len_decrease=0):
+    cols = [" " * len("XXXXXYYZ [X,X,XXXX]Z")]
+    decode_path_len = len(pp.decode_path) - decode_path_len_decrease
+    if decode_path_len > 0:
+        cols.append(" ." * (decode_path_len + 1))
     if isinstance(pp.blob, binary_type):
         blob = hexenc(pp.blob).upper()
         for i in range(0, len(blob), 32):
@@ -1302,7 +1388,14 @@ def pp_console_blob(pp):
         yield " ".join(cols + [", ".join(pp.blob)])
 
 
-def pprint(obj, oids=None, big_blobs=False, with_colours=False):
+def pprint(
+        obj,
+        oids=None,
+        big_blobs=False,
+        with_colours=False,
+        with_decode_path=False,
+        decode_path_only=(),
+):
     """Pretty print object
 
     :param Obj obj: object you want to pretty print
@@ -1313,10 +1406,19 @@ def pprint(obj, oids=None, big_blobs=False, with_colours=False):
                       lines
     :param with_colours: colourize output, if ``termcolor`` library
                          is available
+    :param with_decode_path: print decode path
+    :param decode_path_only: print only that specified decode path
     """
     def _pprint_pps(pps):
         for pp in pps:
             if hasattr(pp, "_fields"):
+                if (
+                    decode_path_only != () and
+                    tuple(
+                        str(p) for p in pp.decode_path[:len(decode_path_only)]
+                    ) != decode_path_only
+                ):
+                    continue
                 if big_blobs:
                     yield pp_console_row(
                         pp,
@@ -1324,8 +1426,13 @@ def pprint(obj, oids=None, big_blobs=False, with_colours=False):
                         with_offsets=True,
                         with_blob=False,
                         with_colours=with_colours,
+                        with_decode_path=with_decode_path,
+                        decode_path_len_decrease=len(decode_path_only),
                     )
-                    for row in pp_console_blob(pp):
+                    for row in pp_console_blob(
+                        pp,
+                        decode_path_len_decrease=len(decode_path_only),
+                    ):
                         yield row
                 else:
                     yield pp_console_row(
@@ -1334,6 +1441,8 @@ def pprint(obj, oids=None, big_blobs=False, with_colours=False):
                         with_offsets=True,
                         with_blob=True,
                         with_colours=with_colours,
+                        with_decode_path=with_decode_path,
+                        decode_path_len_decrease=len(decode_path_only),
                     )
             else:
                 for row in _pprint_pps(pp):
@@ -1546,6 +1655,8 @@ class Boolean(Obj):
             expl_lenindef=self.expl_lenindef,
             bered=self.bered,
         )
+        for pp in self.pps_lenindef(decode_path):
+            yield pp
 
 
 class Integer(Obj):
@@ -1869,6 +1980,8 @@ class Integer(Obj):
             expl_vlen=self.expl_vlen if self.expled else None,
             expl_lenindef=self.expl_lenindef,
         )
+        for pp in self.pps_lenindef(decode_path):
+            yield pp
 
 
 class BitString(Obj):
@@ -1911,6 +2024,14 @@ class BitString(Obj):
     ['nonRepudiation', 'keyEncipherment']
     >>> b.specs
     {'nonRepudiation': 1, 'digitalSignature': 0, 'keyEncipherment': 2}
+
+    .. note::
+
+       Pay attention that BIT STRING can be encoded both in primitive
+       and constructed forms. Decoder always checks constructed form tag
+       additionally to specified primitive one. If BER decoding is
+       :ref:`not enabled <bered_ctx>`, then decoder will fail, because
+       of DER restrictions.
     """
     __slots__ = ("tag_constructed", "specs", "defined")
     tag_default = tag_encode(3)
@@ -1986,9 +2107,7 @@ class BitString(Obj):
                         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))
@@ -2180,7 +2299,8 @@ class BitString(Obj):
         if t == self.tag_constructed:
             if not ctx.get("bered", False):
                 raise DecodeError(
-                    msg="unallowed BER constructed encoding",
+                    "unallowed BER constructed encoding",
+                    klass=self.__class__,
                     decode_path=decode_path,
                     offset=offset,
                 )
@@ -2199,7 +2319,7 @@ class BitString(Obj):
                     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__,
@@ -2225,8 +2345,9 @@ class BitString(Obj):
                         break
                     if vlen > l:
                         raise DecodeError(
-                            msg="chunk out of bounds",
-                            decode_path=len(chunks) - 1,
+                            "chunk out of bounds",
+                            klass=self.__class__,
+                            decode_path=decode_path + (str(len(chunks) - 1),),
                             offset=chunks[-1].offset,
                         )
                 sub_decode_path = decode_path + (str(len(chunks)),)
@@ -2240,7 +2361,8 @@ class BitString(Obj):
                     )
                 except TagMismatch:
                     raise DecodeError(
-                        msg="expected BitString encoded chunk",
+                        "expected BitString encoded chunk",
+                        klass=self.__class__,
                         decode_path=sub_decode_path,
                         offset=sub_offset,
                     )
@@ -2250,7 +2372,8 @@ class BitString(Obj):
                 v = v_tail
             if len(chunks) == 0:
                 raise DecodeError(
-                    msg="no chunks",
+                    "no chunks",
+                    klass=self.__class__,
                     decode_path=decode_path,
                     offset=offset,
                 )
@@ -2259,7 +2382,8 @@ class BitString(Obj):
             for chunk_i, chunk in enumerate(chunks[:-1]):
                 if chunk.bit_len % 8 != 0:
                     raise DecodeError(
-                        msg="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,
                     )
@@ -2324,6 +2448,8 @@ class BitString(Obj):
             yield defined.pps(
                 decode_path=decode_path + (DecodePathDefBy(defined_by),)
             )
+        for pp in self.pps_lenindef(decode_path):
+            yield pp
 
 
 class OctetString(Obj):
@@ -2341,6 +2467,14 @@ class OctetString(Obj):
     pyderasn.BoundsError: unsatisfied bounds: 4 <= 5 <= 4
     >>> OctetString(b"hell", bounds=(4, 4))
     OCTET STRING 4 bytes 68656c6c
+
+    .. note::
+
+       Pay attention that OCTET STRING can be encoded both in primitive
+       and constructed forms. Decoder always checks constructed form tag
+       additionally to specified primitive one. If BER decoding is
+       :ref:`not enabled <bered_ctx>`, then decoder will fail, because
+       of DER restrictions.
     """
     __slots__ = ("tag_constructed", "_bound_min", "_bound_max", "defined")
     tag_default = tag_encode(4)
@@ -2535,7 +2669,8 @@ class OctetString(Obj):
         if t == self.tag_constructed:
             if not ctx.get("bered", False):
                 raise DecodeError(
-                    msg="unallowed BER constructed encoding",
+                    "unallowed BER constructed encoding",
+                    klass=self.__class__,
                     decode_path=decode_path,
                     offset=offset,
                 )
@@ -2554,20 +2689,13 @@ class OctetString(Obj):
                     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
@@ -2580,8 +2708,9 @@ class OctetString(Obj):
                         break
                     if vlen > l:
                         raise DecodeError(
-                            msg="chunk out of bounds",
-                            decode_path=len(chunks) - 1,
+                            "chunk out of bounds",
+                            klass=self.__class__,
+                            decode_path=decode_path + (str(len(chunks) - 1),),
                             offset=chunks[-1].offset,
                         )
                 sub_decode_path = decode_path + (str(len(chunks)),)
@@ -2595,7 +2724,8 @@ class OctetString(Obj):
                     )
                 except TagMismatch:
                     raise DecodeError(
-                        msg="expected OctetString encoded chunk",
+                        "expected OctetString encoded chunk",
+                        klass=self.__class__,
                         decode_path=sub_decode_path,
                         offset=sub_offset,
                     )
@@ -2603,12 +2733,6 @@ class OctetString(Obj):
                 sub_offset += chunk.tlvlen
                 vlen += chunk.tlvlen
                 v = v_tail
-            if len(chunks) == 0:
-                raise DecodeError(
-                    msg="no chunks",
-                    decode_path=decode_path,
-                    offset=offset,
-                )
             try:
                 obj = self.__class__(
                     value=b"".join(bytes(chunk) for chunk in chunks),
@@ -2673,6 +2797,8 @@ class OctetString(Obj):
             yield defined.pps(
                 decode_path=decode_path + (DecodePathDefBy(defined_by),)
             )
+        for pp in self.pps_lenindef(decode_path):
+            yield pp
 
 
 class Null(Obj):
@@ -2805,6 +2931,8 @@ class Null(Obj):
             expl_vlen=self.expl_vlen if self.expled else None,
             expl_lenindef=self.expl_lenindef,
         )
+        for pp in self.pps_lenindef(decode_path):
+            yield pp
 
 
 class ObjectIdentifier(Obj):
@@ -3094,6 +3222,8 @@ class ObjectIdentifier(Obj):
             expl_vlen=self.expl_vlen if self.expled else None,
             expl_lenindef=self.expl_lenindef,
         )
+        for pp in self.pps_lenindef(decode_path):
+            yield pp
 
 
 class Enumerated(Integer):
@@ -3317,6 +3447,8 @@ class CommonString(OctetString):
             expl_vlen=self.expl_vlen if self.expled else None,
             expl_lenindef=self.expl_lenindef,
         )
+        for pp in self.pps_lenindef(decode_path):
+            yield pp
 
 
 class UTF8String(CommonString):
@@ -3327,6 +3459,10 @@ class UTF8String(CommonString):
 
 
 class NumericString(CommonString):
+    """Numeric string
+
+    Its value is properly sanitized: only ASCII digits can be stored.
+    """
     __slots__ = ()
     tag_default = tag_encode(18)
     encoding = "ascii"
@@ -3443,11 +3579,14 @@ class UTCTime(CommonString):
         if isinstance(value, datetime):
             return value.strftime(self.fmt).encode("ascii")
         if isinstance(value, binary_type):
-            value_decoded = value.decode("ascii")
+            try:
+                value_decoded = value.decode("ascii")
+            except (UnicodeEncodeError, UnicodeDecodeError) as err:
+                raise DecodeError("invalid UTCTime encoding")
             if len(value_decoded) == LEN_YYMMDDHHMMSSZ:
                 try:
                     datetime.strptime(value_decoded, self.fmt)
-                except ValueError:
+                except (TypeError, ValueError):
                     raise DecodeError("invalid UTCTime format")
                 return value
             else:
@@ -3510,6 +3649,8 @@ class UTCTime(CommonString):
             expl_vlen=self.expl_vlen if self.expled else None,
             expl_lenindef=self.expl_lenindef,
         )
+        for pp in self.pps_lenindef(decode_path):
+            yield pp
 
 
 class GeneralizedTime(UTCTime):
@@ -3539,11 +3680,14 @@ class GeneralizedTime(UTCTime):
                 self.fmt_ms if value.microsecond > 0 else self.fmt
             ).encode("ascii")
         if isinstance(value, binary_type):
-            value_decoded = value.decode("ascii")
+            try:
+                value_decoded = value.decode("ascii")
+            except (UnicodeEncodeError, UnicodeDecodeError) as err:
+                raise DecodeError("invalid GeneralizedTime encoding")
             if len(value_decoded) == LEN_YYYYMMDDHHMMSSZ:
                 try:
                     datetime.strptime(value_decoded, self.fmt)
-                except ValueError:
+                except (TypeError, ValueError):
                     raise DecodeError(
                         "invalid GeneralizedTime (without ms) format",
                     )
@@ -3551,7 +3695,7 @@ class GeneralizedTime(UTCTime):
             elif len(value_decoded) >= LEN_YYYYMMDDHHMMSSDMZ:
                 try:
                     datetime.strptime(value_decoded, self.fmt_ms)
-                except ValueError:
+                except (TypeError, ValueError):
                     raise DecodeError(
                         "invalid GeneralizedTime (with ms) format",
                     )
@@ -3815,7 +3959,7 @@ class Choice(Obj):
             expl=self._expl,
             default=self.default,
             optional=self.optional,
-            _decoded=(offset, 0, value.tlvlen),
+            _decoded=(offset, 0, value.fulllen),
         )
         obj._value = (choice, value)
         return obj, tail
@@ -3844,6 +3988,8 @@ class Choice(Obj):
         )
         if self.ready:
             yield self.value.pps(decode_path=decode_path + (self.choice,))
+        for pp in self.pps_lenindef(decode_path):
+            yield pp
 
 
 class PrimitiveTypes(Choice):
@@ -3992,29 +4138,27 @@ class Any(Obj):
             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,
@@ -4069,6 +4213,8 @@ class Any(Obj):
             yield defined.pps(
                 decode_path=decode_path + (DecodePathDefBy(defined_by),)
             )
+        for pp in self.pps_lenindef(decode_path):
+            yield pp
 
 
 ########################################################################
@@ -4172,7 +4318,7 @@ class Sequence(Obj):
         ("algorithm", ObjectIdentifier("1.2.3")),
         ("parameters", Any(Null()))
     ))
-    AlgorithmIdentifier SEQUENCE[OBJECT IDENTIFIER 1.2.3, ANY 0500 OPTIONAL]
+    AlgorithmIdentifier SEQUENCE[algorithm: OBJECT IDENTIFIER 1.2.3; parameters: ANY 0500 OPTIONAL]
 
     You can determine if value exists/set in the sequence and take its value:
 
@@ -4193,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.
@@ -4364,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__,
@@ -4395,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
@@ -4415,7 +4560,7 @@ class Sequence(Obj):
                     continue
                 raise
 
-            defined = get_def_by_path(ctx.get("defines", ()), sub_decode_path)
+            defined = get_def_by_path(ctx.get("_defines", ()), sub_decode_path)
             if defined is not None:
                 defined_by, defined_spec = defined
                 if issubclass(value.__class__, SequenceOf):
@@ -4462,20 +4607,20 @@ class Sequence(Obj):
                         )
                     value.defined = (defined_by, defined_value)
 
-            value_len = value.expl_tlvlen if value.expled else value.tlvlen
+            value_len = value.fulllen
             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", ())
@@ -4487,7 +4632,7 @@ class Sequence(Obj):
                 for rel_path, schema in spec_defines:
                     defined = schema.get(value, None)
                     if defined is not None:
-                        ctx.setdefault("defines", []).append((
+                        ctx.setdefault("_defines", []).append((
                             abs_decode_path(sub_decode_path[:-1], rel_path),
                             (value, defined),
                         ))
@@ -4518,6 +4663,7 @@ class Sequence(Obj):
         )
         obj._value = values
         obj.lenindef = lenindef
+        obj.bered = bered
         return obj, tail
 
     def __repr__(self):
@@ -4527,8 +4673,8 @@ class Sequence(Obj):
             _value = self._value.get(name)
             if _value is None:
                 continue
-            cols.append(repr(_value))
-        return "%s[%s]" % (value, ", ".join(cols))
+            cols.append("%s: %s" % (name, repr(_value)))
+        return "%s[%s]" % (value, "; ".join(cols))
 
     def pps(self, decode_path=()):
         yield _pp(
@@ -4555,12 +4701,22 @@ class Sequence(Obj):
             if value is None:
                 continue
             yield value.pps(decode_path=decode_path + (name,))
+        for pp in self.pps_lenindef(decode_path):
+            yield pp
 
 
 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)
@@ -4591,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__,
@@ -4621,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:
@@ -4652,13 +4813,33 @@ class Set(Sequence):
                 decode_path=sub_decode_path,
                 ctx=ctx,
             )
-            value_len = value.expl_tlvlen if value.expled else value.tlvlen
+            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,
@@ -4667,16 +4848,26 @@ class Set(Sequence):
             optional=self.optional,
             _decoded=(offset, llen, vlen + (EOC_LEN if lenindef else 0)),
         )
+        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
         obj._value = values
         if not obj.ready:
             raise DecodeError(
-                msg="not all values are ready",
+                "not all values are ready",
                 klass=self.__class__,
                 decode_path=decode_path,
                 offset=offset,
             )
-        obj.lenindef = lenindef
-        return obj, (v[EOC_LEN:] if lenindef else tail)
+        obj.bered = bered
+        return obj, tail
 
 
 class SequenceOf(Obj):
@@ -4859,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:
@@ -4878,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__,
@@ -4909,34 +5101,68 @@ 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.expl_tlvlen if value.expled else value.tlvlen
+            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)
-        obj = self.__class__(
-            value=_value,
-            schema=spec,
-            bounds=(self._bound_min, self._bound_max),
-            impl=self.tag,
-            expl=self._expl,
-            default=self.default,
-            optional=self.optional,
-            _decoded=(offset, llen, vlen),
-        )
-        obj.lenindef = lenindef
-        return obj, (v[EOC_LEN:] if lenindef else tail)
+        try:
+            obj = self.__class__(
+                value=_value,
+                schema=spec,
+                bounds=(self._bound_min, self._bound_max),
+                impl=self.tag,
+                expl=self._expl,
+                default=self.default,
+                optional=self.optional,
+                _decoded=(offset, llen, vlen + (EOC_LEN if lenindef else 0)),
+            )
+        except BoundsError as err:
+            raise DecodeError(
+                msg=str(err),
+                klass=self.__class__,
+                decode_path=decode_path,
+                offset=offset,
+            )
+        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:]
+        obj.bered = bered
+        return obj, tail
 
     def __repr__(self):
         return "%s[%s]" % (
@@ -4966,6 +5192,8 @@ class SequenceOf(Obj):
         )
         for i, value in enumerate(self._value):
             yield value.pps(decode_path=decode_path + (str(i),))
+        for pp in self.pps_lenindef(decode_path):
+            yield pp
 
 
 class SetOf(SequenceOf):
@@ -4983,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
@@ -5019,10 +5257,21 @@ def generic_decoder():  # pragma: no cover
         __slots__ = ()
         schema = choice
 
-    def pprint_any(obj, oids=None, with_colours=False):
+    def pprint_any(
+            obj,
+            oids=None,
+            with_colours=False,
+            with_decode_path=False,
+            decode_path_only=(),
+    ):
         def _pprint_pps(pps):
             for pp in pps:
                 if hasattr(pp, "_fields"):
+                    if (
+                        decode_path_only != () and
+                        pp.decode_path[:len(decode_path_only)] != decode_path_only
+                    ):
+                        continue
                     if pp.asn1_type_name == Choice.asn1_type_name:
                         continue
                     pp_kwargs = pp._asdict()
@@ -5034,8 +5283,13 @@ def generic_decoder():  # pragma: no cover
                         with_offsets=True,
                         with_blob=False,
                         with_colours=with_colours,
+                        with_decode_path=with_decode_path,
+                        decode_path_len_decrease=len(decode_path_only),
                     )
-                    for row in pp_console_blob(pp):
+                    for row in pp_console_blob(
+                        pp,
+                        decode_path_len_decrease=len(decode_path_only),
+                    ):
                         yield row
                 else:
                     for row in _pprint_pps(pp):
@@ -5067,9 +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",
+        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"),
@@ -5086,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)
@@ -5094,6 +5365,11 @@ def main():  # pragma: no cover
         obj,
         oids=oids,
         with_colours=True if environ.get("NO_COLOR") is None else False,
+        with_decode_path=args.print_decode_path,
+        decode_path_only=(
+            () if args.decode_path_only is None else
+            tuple(args.decode_path_only.split(":"))
+        ),
     ))
     if tail != b"":
         print("\nTrailing data: %s" % hexenc(tail))