]> Cypherpunks.ru repositories - pyderasn.git/blobdiff - pyderasn.py
BER usage documentation
[pyderasn.git] / pyderasn.py
index 6be00cb0c24888dc463112354744cee7c379b93c..f7e6c4c53f9cb5d5316f555ca42658b0301d4308 100755 (executable)
@@ -1,6 +1,6 @@
 #!/usr/bin/env python
 # coding: utf-8
-# PyDERASN -- Python ASN.1 DER codec with abstract structures
+# PyDERASN -- Python ASN.1 DER/BER codec with abstract structures
 # Copyright (C) 2017-2018 Sergey Matveev <stargrave@stargrave.org>
 #
 # This program is free software: you can redistribute it and/or modify
 # You should have received a copy of the GNU Lesser General Public
 # License along with this program.  If not, see
 # <http://www.gnu.org/licenses/>.
-"""Python ASN.1 DER codec with abstract structures
+"""Python ASN.1 DER/BER codec with abstract structures
 
-This library allows you to marshal and unmarshal various structures in
-ASN.1 DER format, like this:
+This library allows you to marshal various structures in ASN.1 DER
+format, unmarshal them in BER/CER/DER ones.
 
     >>> i = Integer(123)
     >>> raw = i.encode()
@@ -193,7 +193,7 @@ explicit tag. If you want to know information about it, then use:
 lesser than ``offset``), ``expl_tlen``, ``expl_llen``, ``expl_vlen``
 (that actually equals to ordinary ``tlvlen``).
 
-When error occurs, then :py:exc:`pyderasn.DecodeError` is raised.
+When error occurs, :py:exc:`pyderasn.DecodeError` is raised.
 
 .. _ctx:
 
@@ -206,6 +206,7 @@ decoding process.
 
 Currently available context options:
 
+* :ref:`bered <bered_ctx>`
 * :ref:`defines_by_path <defines_by_path_ctx>`
 * :ref:`strict_default_existence <strict_default_existence_ctx>`
 
@@ -363,6 +364,33 @@ First function is useful for path construction when some automatic
 decoding is already done. ``any`` means literally any value it meet --
 useful for SEQUENCE/SET OF-s.
 
+.. _bered_ctx:
+
+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
+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.
+* 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
+  contain it.
+* If object has an indefinite length encoded explicit tag, then
+  ``expl_lenindef`` is set to True.
+
+EOC (end-of-contents) token's length is taken in advance in object's
+value length.
+
 Primitive types
 ---------------
 
@@ -608,7 +636,7 @@ class NotEnoughData(DecodeError):
     pass
 
 
-class LenIndefiniteForm(DecodeError):
+class LenIndefForm(DecodeError):
     pass
 
 
@@ -803,6 +831,11 @@ def len_encode(l):
 
 
 def len_decode(data):
+    """Decode length
+
+    :returns: (decoded length, length's length, remaining data)
+    :raises LenIndefForm: if indefinite form encoding is met
+    """
     if len(data) == 0:
         raise NotEnoughData("no data at all")
     first_octet = byte2int(data)
@@ -812,7 +845,7 @@ def len_decode(data):
     if octets_num + 1 > len(data):
         raise NotEnoughData("encoded length is longer than data")
     if octets_num == 0:
-        raise LenIndefiniteForm()
+        raise LenIndefForm()
     if byte2int(data[1:]) == 0:
         raise DecodeError("leading zeros")
     l = 0
@@ -849,8 +882,9 @@ class Obj(object):
         "offset",
         "llen",
         "vlen",
+        "expl_lenindef",
+        "lenindef",
         "bered",
-        "expl_bered",
     )
 
     def __init__(
@@ -872,8 +906,9 @@ class Obj(object):
         self.optional = optional
         self.offset, self.llen, self.vlen = _decoded
         self.default = None
+        self.expl_lenindef = False
+        self.lenindef = False
         self.bered = False
-        self.expl_bered = False
 
     @property
     def ready(self):  # pragma: no cover
@@ -984,7 +1019,7 @@ class Obj(object):
                 )
             try:
                 l, llen, v = len_decode(lv)
-            except LenIndefiniteForm as err:
+            except LenIndefForm as err:
                 if not ctx.get("bered", False):
                     raise err.__class__(
                         msg=err.msg,
@@ -1012,7 +1047,7 @@ class Obj(object):
                         offset=offset,
                     )
                 obj.vlen += EOC_LEN
-                obj.expl_bered = True
+                obj.expl_lenindef = True
             except DecodeError as err:
                 raise err.__class__(
                     msg=err.msg,
@@ -1054,7 +1089,7 @@ class Obj(object):
 
     @property
     def expl_llen(self):
-        if self.expl_bered:
+        if self.expl_lenindef:
             return 1
         return len(len_encode(self.tlvlen))
 
@@ -1116,6 +1151,9 @@ PP = namedtuple("PP", (
     "expl_tlen",
     "expl_llen",
     "expl_vlen",
+    "expl_lenindef",
+    "lenindef",
+    "bered",
 ))
 
 
@@ -1137,6 +1175,9 @@ def _pp(
         expl_tlen=None,
         expl_llen=None,
         expl_vlen=None,
+        expl_lenindef=False,
+        lenindef=False,
+        bered=False,
 ):
     return PP(
         asn1_type_name,
@@ -1156,6 +1197,9 @@ def _pp(
         expl_tlen,
         expl_llen,
         expl_vlen,
+        expl_lenindef,
+        lenindef,
+        bered,
     )
 
 
@@ -1181,7 +1225,17 @@ def pp_console_row(
         )
         cols.append(_colorize(col, "red", with_colours, ()))
         col = "[%d,%d,%4d]" % (pp.tlen, pp.llen, pp.vlen)
-        cols.append(_colorize(col, "green", with_colours, ()))
+        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(col)
     if len(pp.decode_path) > 0:
         cols.append(" ." * (len(pp.decode_path)))
         ent = pp.decode_path[-1]
@@ -1209,6 +1263,8 @@ def pp_console_row(
         cols.append(_colorize(col, "blue", with_colours))
     if pp.asn1_type_name.replace(" ", "") != pp.obj_name.upper():
         cols.append(_colorize(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))
     if pp.value is not None:
         value = pp.value
@@ -1232,7 +1288,7 @@ def pp_console_row(
 
 
 def pp_console_blob(pp):
-    cols = [" " * len("XXXXXYY [X,X,XXXX]")]
+    cols = [" " * len("XXXXXYY [X,X,XXXX]YY")]
     if len(pp.decode_path) > 0:
         cols.append(" ." * (len(pp.decode_path) + 1))
     if isinstance(pp.blob, binary_type):
@@ -1487,6 +1543,8 @@ class Boolean(Obj):
             expl_tlen=self.expl_tlen if self.expled else None,
             expl_llen=self.expl_llen if self.expled else None,
             expl_vlen=self.expl_vlen if self.expled else None,
+            expl_lenindef=self.expl_lenindef,
+            bered=self.bered,
         )
 
 
@@ -1809,6 +1867,7 @@ class Integer(Obj):
             expl_tlen=self.expl_tlen if self.expled else None,
             expl_llen=self.expl_llen if self.expled else None,
             expl_vlen=self.expl_vlen if self.expled else None,
+            expl_lenindef=self.expl_lenindef,
         )
 
 
@@ -1824,6 +1883,8 @@ class BitString(Obj):
     >>> b.bit_len
     88
 
+    >>> BitString("'0A3B5F291CD'H")
+    BIT STRING 44 bits 0a3b5f291cd0
     >>> b = BitString("'010110000000'B")
     BIT STRING 12 bits 5800
     >>> b.bit_len
@@ -1912,21 +1973,25 @@ class BitString(Obj):
         if isinstance(value, (string_types, binary_type)):
             if (
                     isinstance(value, string_types) and
-                    value.startswith("'") and
-                    value.endswith("'B")
+                    value.startswith("'")
             ):
-                value = value[1:-2]
-                if not set(value) <= set(("0", "1")):
-                    raise ValueError("B's coding contains unacceptable chars")
-                return self._bits2octets(value)
+                if value.endswith("'B"):
+                    value = value[1:-2]
+                    if not set(value) <= set(("0", "1")):
+                        raise ValueError("B's coding contains unacceptable chars")
+                    return self._bits2octets(value)
+                elif value.endswith("'H"):
+                    value = value[1:-2]
+                    return (
+                        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):
                 return (len(value) * 8, value)
             else:
-                raise InvalidValueType((
-                    self.__class__,
-                    string_types,
-                    binary_type,
-                ))
+                raise InvalidValueType((self.__class__, string_types, binary_type))
         if isinstance(value, tuple):
             if (
                     len(value) == 2 and
@@ -2121,12 +2186,12 @@ class BitString(Obj):
                 )
             if tag_only:
                 return
-            eoc_expected = False
+            lenindef = False
             try:
                 l, llen, v = len_decode(lv)
-            except LenIndefiniteForm:
+            except LenIndefForm:
                 llen, l, v = 1, 0, lv[1:]
-                eoc_expected = True
+                lenindef = True
             except DecodeError as err:
                 raise err.__class__(
                     msg=err.msg,
@@ -2141,7 +2206,7 @@ class BitString(Obj):
                     decode_path=decode_path,
                     offset=offset,
                 )
-            if not eoc_expected and l == 0:
+            if not lenindef and l == 0:
                 raise NotEnoughData(
                     "zero length",
                     klass=self.__class__,
@@ -2152,7 +2217,7 @@ class BitString(Obj):
             sub_offset = offset + tlen + llen
             vlen = 0
             while True:
-                if eoc_expected:
+                if lenindef:
                     if v[:EOC_LEN].tobytes() == EOC:
                         break
                 else:
@@ -2210,10 +2275,11 @@ class BitString(Obj):
                 default=self.default,
                 optional=self.optional,
                 _specs=self.specs,
-                _decoded=(offset, llen, vlen + (EOC_LEN if eoc_expected else 0)),
+                _decoded=(offset, llen, vlen + (EOC_LEN if lenindef else 0)),
             )
+            obj.lenindef = lenindef
             obj.bered = True
-            return obj, v[EOC_LEN if eoc_expected else 0:]
+            return obj, (v[EOC_LEN:] if lenindef else v)
         raise TagMismatch(
             klass=self.__class__,
             decode_path=decode_path,
@@ -2249,6 +2315,9 @@ class BitString(Obj):
             expl_tlen=self.expl_tlen if self.expled else None,
             expl_llen=self.expl_llen if self.expled else None,
             expl_vlen=self.expl_vlen if self.expled else None,
+            expl_lenindef=self.expl_lenindef,
+            lenindef=self.lenindef,
+            bered=self.bered,
         )
         defined_by, defined = self.defined or (None, None)
         if defined_by is not None:
@@ -2472,12 +2541,12 @@ class OctetString(Obj):
                 )
             if tag_only:
                 return
-            eoc_expected = False
+            lenindef = False
             try:
                 l, llen, v = len_decode(lv)
-            except LenIndefiniteForm:
+            except LenIndefForm:
                 llen, l, v = 1, 0, lv[1:]
-                eoc_expected = True
+                lenindef = True
             except DecodeError as err:
                 raise err.__class__(
                     msg=err.msg,
@@ -2492,7 +2561,7 @@ class OctetString(Obj):
                     decode_path=decode_path,
                     offset=offset,
                 )
-            if not eoc_expected and l == 0:
+            if not lenindef and l == 0:
                 raise NotEnoughData(
                     "zero length",
                     klass=self.__class__,
@@ -2503,7 +2572,7 @@ class OctetString(Obj):
             sub_offset = offset + tlen + llen
             vlen = 0
             while True:
-                if eoc_expected:
+                if lenindef:
                     if v[:EOC_LEN].tobytes() == EOC:
                         break
                 else:
@@ -2548,7 +2617,7 @@ class OctetString(Obj):
                     expl=self._expl,
                     default=self.default,
                     optional=self.optional,
-                    _decoded=(offset, llen, vlen + (EOC_LEN if eoc_expected else 0)),
+                    _decoded=(offset, llen, vlen + (EOC_LEN if lenindef else 0)),
                 )
             except DecodeError as err:
                 raise DecodeError(
@@ -2564,8 +2633,9 @@ class OctetString(Obj):
                     decode_path=decode_path,
                     offset=offset,
                 )
+            obj.lenindef = lenindef
             obj.bered = True
-            return obj, v[EOC_LEN if eoc_expected else 0:]
+            return obj, (v[EOC_LEN:] if lenindef else v)
         raise TagMismatch(
             klass=self.__class__,
             decode_path=decode_path,
@@ -2594,6 +2664,9 @@ class OctetString(Obj):
             expl_tlen=self.expl_tlen if self.expled else None,
             expl_llen=self.expl_llen if self.expled else None,
             expl_vlen=self.expl_vlen if self.expled else None,
+            expl_lenindef=self.expl_lenindef,
+            lenindef=self.lenindef,
+            bered=self.bered,
         )
         defined_by, defined = self.defined or (None, None)
         if defined_by is not None:
@@ -2730,6 +2803,7 @@ class Null(Obj):
             expl_tlen=self.expl_tlen if self.expled else None,
             expl_llen=self.expl_llen if self.expled else None,
             expl_vlen=self.expl_vlen if self.expled else None,
+            expl_lenindef=self.expl_lenindef,
         )
 
 
@@ -3018,6 +3092,7 @@ class ObjectIdentifier(Obj):
             expl_tlen=self.expl_tlen if self.expled else None,
             expl_llen=self.expl_llen if self.expled else None,
             expl_vlen=self.expl_vlen if self.expled else None,
+            expl_lenindef=self.expl_lenindef,
         )
 
 
@@ -3236,6 +3311,11 @@ class CommonString(OctetString):
             tlen=self.tlen,
             llen=self.llen,
             vlen=self.vlen,
+            expl_offset=self.expl_offset if self.expled else None,
+            expl_tlen=self.expl_tlen if self.expled else None,
+            expl_llen=self.expl_llen if self.expled else None,
+            expl_vlen=self.expl_vlen if self.expled else None,
+            expl_lenindef=self.expl_lenindef,
         )
 
 
@@ -3424,6 +3504,11 @@ class UTCTime(CommonString):
             tlen=self.tlen,
             llen=self.llen,
             vlen=self.vlen,
+            expl_offset=self.expl_offset if self.expled else None,
+            expl_tlen=self.expl_tlen if self.expled else None,
+            expl_llen=self.expl_llen if self.expled else None,
+            expl_vlen=self.expl_vlen if self.expled else None,
+            expl_lenindef=self.expl_lenindef,
         )
 
 
@@ -3755,6 +3840,7 @@ class Choice(Obj):
             tlen=self.tlen,
             llen=self.llen,
             vlen=self.vlen,
+            expl_lenindef=self.expl_lenindef,
         )
         if self.ready:
             yield self.value.pps(decode_path=decode_path + (self.choice,))
@@ -3886,7 +3972,49 @@ class Any(Obj):
     def _decode(self, tlv, offset, decode_path, ctx, tag_only):
         try:
             t, tlen, lv = tag_strip(tlv)
+        except DecodeError as err:
+            raise err.__class__(
+                msg=err.msg,
+                klass=self.__class__,
+                decode_path=decode_path,
+                offset=offset,
+            )
+        try:
             l, llen, v = len_decode(lv)
+        except LenIndefForm as err:
+            if not ctx.get("bered", False):
+                raise err.__class__(
+                    msg=err.msg,
+                    klass=self.__class__,
+                    decode_path=decode_path,
+                    offset=offset,
+                )
+            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
         except DecodeError as err:
             raise err.__class__(
                 msg=err.msg,
@@ -3933,6 +4061,8 @@ class Any(Obj):
             expl_tlen=self.expl_tlen if self.expled else None,
             expl_llen=self.expl_llen if self.expled else None,
             expl_vlen=self.expl_vlen if self.expled else None,
+            expl_lenindef=self.expl_lenindef,
+            lenindef=self.lenindef,
         )
         defined_by, defined = self.defined or (None, None)
         if defined_by is not None:
@@ -4233,8 +4363,19 @@ class Sequence(Obj):
             )
         if tag_only:
             return
+        lenindef = False
         try:
             l, llen, v = len_decode(lv)
+        except LenIndefForm as err:
+            if not ctx.get("bered", False):
+                raise err.__class__(
+                    msg=err.msg,
+                    klass=self.__class__,
+                    decode_path=decode_path,
+                    offset=offset,
+                )
+            l, llen, v = 0, 1, lv[1:]
+            lenindef = True
         except DecodeError as err:
             raise err.__class__(
                 msg=err.msg,
@@ -4249,11 +4390,16 @@ class Sequence(Obj):
                 decode_path=decode_path,
                 offset=offset,
             )
-        v, tail = v[:l], v[l:]
+        if not lenindef:
+            v, tail = v[:l], v[l:]
+        vlen = 0
         sub_offset = offset + tlen + llen
         values = {}
         for name, spec in self.specs.items():
-            if len(v) == 0 and spec.optional:
+            if spec.optional and (
+                    (lenindef and v[:EOC_LEN].tobytes() == EOC) or
+                    len(v) == 0
+            ):
                 continue
             sub_decode_path = decode_path + (name,)
             try:
@@ -4316,7 +4462,9 @@ class Sequence(Obj):
                         )
                     value.defined = (defined_by, defined_value)
 
-            sub_offset += (value.expl_tlvlen if value.expled else value.tlvlen)
+            value_len = value.expl_tlvlen if value.expled else value.tlvlen
+            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):
@@ -4343,7 +4491,17 @@ class Sequence(Obj):
                             abs_decode_path(sub_decode_path[:-1], rel_path),
                             (value, defined),
                         ))
-        if len(v) > 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:]
+            vlen += EOC_LEN
+        elif len(v) > 0:
             raise DecodeError(
                 "remaining data",
                 klass=self.__class__,
@@ -4356,9 +4514,10 @@ class Sequence(Obj):
             expl=self._expl,
             default=self.default,
             optional=self.optional,
-            _decoded=(offset, llen, l),
+            _decoded=(offset, llen, vlen),
         )
         obj._value = values
+        obj.lenindef = lenindef
         return obj, tail
 
     def __repr__(self):
@@ -4388,6 +4547,8 @@ class Sequence(Obj):
             expl_tlen=self.expl_tlen if self.expled else None,
             expl_llen=self.expl_llen if self.expled else None,
             expl_vlen=self.expl_vlen if self.expled else None,
+            expl_lenindef=self.expl_lenindef,
+            lenindef=self.lenindef,
         )
         for name in self.specs:
             value = self._value.get(name)
@@ -4429,8 +4590,19 @@ class Set(Sequence):
             )
         if tag_only:
             return
+        lenindef = False
         try:
             l, llen, v = len_decode(lv)
+        except LenIndefForm as err:
+            if not ctx.get("bered", False):
+                raise err.__class__(
+                    msg=err.msg,
+                    klass=self.__class__,
+                    decode_path=decode_path,
+                    offset=offset,
+                )
+            l, llen, v = 0, 1, lv[1:]
+            lenindef = True
         except DecodeError as err:
             raise err.__class__(
                 msg=err.msg,
@@ -4444,11 +4616,15 @@ class Set(Sequence):
                 klass=self.__class__,
                 offset=offset,
             )
-        v, tail = v[:l], v[l:]
+        if not lenindef:
+            v, tail = v[:l], v[l:]
+        vlen = 0
         sub_offset = offset + tlen + llen
         values = {}
         specs_items = self.specs.items
         while len(v) > 0:
+            if lenindef and v[:EOC_LEN].tobytes() == EOC:
+                break
             for name, spec in specs_items():
                 sub_decode_path = decode_path + (name,)
                 try:
@@ -4476,9 +4652,9 @@ class Set(Sequence):
                 decode_path=sub_decode_path,
                 ctx=ctx,
             )
-            sub_offset += (
-                value.expl_tlvlen if value.expled else value.tlvlen
-            )
+            value_len = value.expl_tlvlen if value.expled else value.tlvlen
+            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
@@ -4489,7 +4665,7 @@ class Set(Sequence):
             expl=self._expl,
             default=self.default,
             optional=self.optional,
-            _decoded=(offset, llen, l),
+            _decoded=(offset, llen, vlen + (EOC_LEN if lenindef else 0)),
         )
         obj._value = values
         if not obj.ready:
@@ -4499,7 +4675,8 @@ class Set(Sequence):
                 decode_path=decode_path,
                 offset=offset,
             )
-        return obj, tail
+        obj.lenindef = lenindef
+        return obj, (v[EOC_LEN:] if lenindef else tail)
 
 
 class SequenceOf(Obj):
@@ -4700,8 +4877,19 @@ class SequenceOf(Obj):
             )
         if tag_only:
             return
+        lenindef = False
         try:
             l, llen, v = len_decode(lv)
+        except LenIndefForm as err:
+            if not ctx.get("bered", False):
+                raise err.__class__(
+                    msg=err.msg,
+                    klass=self.__class__,
+                    decode_path=decode_path,
+                    offset=offset,
+                )
+            l, llen, v = 0, 1, lv[1:]
+            lenindef = True
         except DecodeError as err:
             raise err.__class__(
                 msg=err.msg,
@@ -4716,11 +4904,15 @@ class SequenceOf(Obj):
                 decode_path=decode_path,
                 offset=offset,
             )
-        v, tail = v[:l], v[l:]
+        if not lenindef:
+            v, tail = v[:l], v[l:]
+        vlen = 0
         sub_offset = offset + tlen + llen
         _value = []
         spec = self.spec
         while len(v) > 0:
+            if lenindef and v[:EOC_LEN].tobytes() == EOC:
+                break
             value, v_tail = spec.decode(
                 v,
                 sub_offset,
@@ -4728,7 +4920,9 @@ class SequenceOf(Obj):
                 decode_path=decode_path + (str(len(_value)),),
                 ctx=ctx,
             )
-            sub_offset += (value.expl_tlvlen if value.expled else value.tlvlen)
+            value_len = value.expl_tlvlen if value.expled else value.tlvlen
+            sub_offset += value_len
+            vlen += value_len
             v = v_tail
             _value.append(value)
         obj = self.__class__(
@@ -4739,9 +4933,10 @@ class SequenceOf(Obj):
             expl=self._expl,
             default=self.default,
             optional=self.optional,
-            _decoded=(offset, llen, l),
+            _decoded=(offset, llen, vlen),
         )
-        return obj, tail
+        obj.lenindef = lenindef
+        return obj, (v[EOC_LEN:] if lenindef else tail)
 
     def __repr__(self):
         return "%s[%s]" % (
@@ -4766,6 +4961,8 @@ class SequenceOf(Obj):
             expl_tlen=self.expl_tlen if self.expled else None,
             expl_llen=self.expl_llen if self.expled else None,
             expl_vlen=self.expl_vlen if self.expled else None,
+            expl_lenindef=self.expl_lenindef,
+            lenindef=self.lenindef,
         )
         for i, value in enumerate(self._value):
             yield value.pps(decode_path=decode_path + (str(i),))
@@ -4849,7 +5046,7 @@ def generic_decoder():  # pragma: no cover
 
 def main():  # pragma: no cover
     import argparse
-    parser = argparse.ArgumentParser(description="PyDERASN ASN.1 DER decoder")
+    parser = argparse.ArgumentParser(description="PyDERASN ASN.1 BER/DER decoder")
     parser.add_argument(
         "--skip",
         type=int,
@@ -4868,6 +5065,11 @@ def main():  # pragma: no cover
         "--defines-by-path",
         help="Python path to decoder's defines_by_path",
     )
+    parser.add_argument(
+        "--nobered",
+        action='store_true',
+        help="Disallow BER encoding",
+    )
     parser.add_argument(
         "DERFile",
         type=argparse.FileType("rb"),
@@ -4884,13 +5086,10 @@ def main():  # pragma: no cover
         pprinter = partial(pprint, big_blobs=True)
     else:
         schema, pprinter = generic_decoder()
-    obj, tail = schema().decode(
-        der,
-        ctx=(
-            None if args.defines_by_path is None else
-            {"defines_by_path": obj_by_path(args.defines_by_path)}
-        ),
-    )
+    ctx = {"bered": not args.nobered}
+    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)
     print(pprinter(
         obj,
         oids=oids,