]> Cypherpunks.ru repositories - pyderasn.git/blobdiff - pyderasn.py
Fix EOC repr under Py2
[pyderasn.git] / pyderasn.py
index b28792c9e00e4c8459eff239210d0aaf9794c368..188e2ac49e1ca06582d2fba6270515461f158940 100755 (executable)
@@ -369,10 +369,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
@@ -607,6 +603,8 @@ TagClassReprs = {
 }
 EOC = b"\x00\x00"
 EOC_LEN = len(EOC)
+LENINDEF = b"\x80"  # length indefinite mark
+LENINDEF_PP_CHAR = "I" if PY2 else "∞"
 
 
 ########################################################################
@@ -914,9 +912,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
@@ -1058,7 +1054,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,
                     )
@@ -1121,6 +1118,33 @@ class Obj(object):
     def expl_tlvlen(self):
         return self.expl_tlen + self.expl_llen + self.expl_vlen
 
+    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
@@ -1232,25 +1256,22 @@ def pp_console_row(
 ):
     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)
+        col = "[%d,%d,%4d]%s" % (
+            pp.tlen,
+            pp.llen,
+            pp.vlen,
+            LENINDEF_PP_CHAR if pp.lenindef else " "
         )
+        col = _colorize(col, "green", with_colours, ())
         cols.append(col)
     if len(pp.decode_path) > 0:
         cols.append(" ." * (len(pp.decode_path)))
@@ -1304,7 +1325,7 @@ def pp_console_row(
 
 
 def pp_console_blob(pp):
-    cols = [" " * len("XXXXXYY [X,X,XXXX]YY")]
+    cols = [" " * len("XXXXXYYZ [X,X,XXXX]Z")]
     if len(pp.decode_path) > 0:
         cols.append(" ." * (len(pp.decode_path) + 1))
     if isinstance(pp.blob, binary_type):
@@ -1562,6 +1583,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):
@@ -1885,6 +1908,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):
@@ -1927,6 +1952,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)
@@ -2002,9 +2035,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))
@@ -2196,7 +2227,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,
                 )
@@ -2215,7 +2247,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__,
@@ -2241,8 +2273,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)),)
@@ -2256,7 +2289,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,
                     )
@@ -2266,7 +2300,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,
                 )
@@ -2275,7 +2310,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,
                     )
@@ -2340,6 +2376,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):
@@ -2357,6 +2395,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)
@@ -2551,7 +2597,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,
                 )
@@ -2570,20 +2617,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
@@ -2596,8 +2636,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)),)
@@ -2611,7 +2652,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,
                     )
@@ -2619,12 +2661,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),
@@ -2689,6 +2725,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):
@@ -2821,6 +2859,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):
@@ -3110,6 +3150,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):
@@ -3333,6 +3375,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):
@@ -3530,6 +3574,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):
@@ -3864,6 +3910,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):
@@ -4012,29 +4060,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,
@@ -4089,6 +4135,8 @@ class Any(Obj):
             yield defined.pps(
                 decode_path=decode_path + (DecodePathDefBy(defined_by),)
             )
+        for pp in self.pps_lenindef(decode_path):
+            yield pp
 
 
 ########################################################################
@@ -4192,7 +4240,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:
 
@@ -4547,8 +4595,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(
@@ -4575,6 +4623,8 @@ 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):
@@ -4688,15 +4738,24 @@ class Set(Sequence):
             _decoded=(offset, llen, vlen + (EOC_LEN if lenindef else 0)),
         )
         obj._value = values
+        if lenindef:
+            if v[:EOC_LEN].tobytes() != EOC:
+                raise DecodeError(
+                    "no EOC",
+                    klass=self.__class__,
+                    decode_path=decode_path,
+                    offset=offset,
+                )
+            tail = v[EOC_LEN:]
+            obj.lenindef = True
         if not obj.ready:
             raise DecodeError(
-                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)
+        return obj, tail
 
 
 class SequenceOf(Obj):
@@ -4953,10 +5012,19 @@ class SequenceOf(Obj):
             expl=self._expl,
             default=self.default,
             optional=self.optional,
-            _decoded=(offset, llen, vlen),
+            _decoded=(offset, llen, vlen + (EOC_LEN if lenindef else 0)),
         )
-        obj.lenindef = lenindef
-        return obj, (v[EOC_LEN:] if lenindef else tail)
+        if lenindef:
+            if v[:EOC_LEN].tobytes() != EOC:
+                raise DecodeError(
+                    "no EOC",
+                    klass=self.__class__,
+                    decode_path=decode_path,
+                    offset=offset,
+                )
+            obj.lenindef = True
+            tail = v[EOC_LEN:]
+        return obj, tail
 
     def __repr__(self):
         return "%s[%s]" % (
@@ -4986,6 +5054,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):