]> Cypherpunks.ru repositories - pyderasn.git/blobdiff - pyderasn.py
More Obj's documentation
[pyderasn.git] / pyderasn.py
index 254065491fb3772a4937daa850329a0c673cc83e..32d942460436403c3bde2725f92aa370e63e715f 100755 (executable)
@@ -1,12 +1,11 @@
 #!/usr/bin/env python
 # coding: utf-8
 # PyDERASN -- Python ASN.1 DER/BER codec with abstract structures
 #!/usr/bin/env python
 # coding: utf-8
 # PyDERASN -- Python ASN.1 DER/BER codec with abstract structures
-# Copyright (C) 2017-2019 Sergey Matveev <stargrave@stargrave.org>
+# Copyright (C) 2017-2020 Sergey Matveev <stargrave@stargrave.org>
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU Lesser General Public License as
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU Lesser General Public License as
-# published by the Free Software Foundation, either version 3 of the
-# License, or (at your option) any later version.
+# published by the Free Software Foundation, version 3 of the License.
 #
 # This program is distributed in the hope that it will be useful,
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
 #
 # This program is distributed in the hope that it will be useful,
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
@@ -14,8 +13,7 @@
 # GNU Lesser General Public License for more details.
 #
 # You should have received a copy of the GNU Lesser General Public
 # GNU Lesser General Public License for more details.
 #
 # 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/>.
+# License along with this program.  If not, see <http://www.gnu.org/licenses/>.
 """Python ASN.1 DER/BER codec with abstract structures
 
 This library allows you to marshal various structures in ASN.1 DER
 """Python ASN.1 DER/BER codec with abstract structures
 
 This library allows you to marshal various structures in ASN.1 DER
@@ -348,6 +346,8 @@ DEFINED BY some previously met ObjectIdentifier. This library provides
 ability to specify mapping between some OID and field that must be
 decoded with specific specification.
 
 ability to specify mapping between some OID and field that must be
 decoded with specific specification.
 
+.. _defines:
+
 defines kwarg
 _____________
 
 defines kwarg
 _____________
 
@@ -421,15 +421,15 @@ value must be sequence of following tuples::
 
 where ``decode_path`` is a tuple holding so-called decode path to the
 exact :py:class:`pyderasn.ObjectIdentifier` field you want to apply
 
 where ``decode_path`` is a tuple holding so-called decode path to the
 exact :py:class:`pyderasn.ObjectIdentifier` field you want to apply
-``defines``, holding exactly the same value as accepted in its keyword
-argument.
+``defines``, holding exactly the same value as accepted in its
+:ref:`keyword argument <defines>`.
 
 For example, again for CMS, you want to automatically decode
 ``SignedData`` and CMC's (:rfc:`5272`) ``PKIData`` and ``PKIResponse``
 structures it may hold. Also, automatically decode ``controlSequence``
 of ``PKIResponse``::
 
 
 For example, again for CMS, you want to automatically decode
 ``SignedData`` and CMC's (:rfc:`5272`) ``PKIData`` and ``PKIResponse``
 structures it may hold. Also, automatically decode ``controlSequence``
 of ``PKIResponse``::
 
-    content_info, tail = ContentInfo().decode(data, defines_by_path=(
+    content_info, tail = ContentInfo().decode(data, ctx={"defines_by_path": (
         (
             ("contentType",),
             ((("content",), {id_signedData: SignedData()}),),
         (
             ("contentType",),
             ((("content",), {id_signedData: SignedData()}),),
@@ -464,7 +464,7 @@ of ``PKIResponse``::
                 id_cmc_transactionId: TransactionId(),
             })),
         ),
                 id_cmc_transactionId: TransactionId(),
             })),
         ),
-    ))
+    )})
 
 Pay attention for :py:class:`pyderasn.DecodePathDefBy` and ``any``.
 First function is useful for path construction when some automatic
 
 Pay attention for :py:class:`pyderasn.DecodePathDefBy` and ``any``.
 First function is useful for path construction when some automatic
@@ -517,6 +517,11 @@ lengths will be invalid in that case.
    This option should be used only for skipping some decode errors, just
    to see the decoded structure somehow.
 
    This option should be used only for skipping some decode errors, just
    to see the decoded structure somehow.
 
+Base Obj
+--------
+.. autoclass:: pyderasn.Obj
+   :members:
+
 Primitive types
 ---------------
 
 Primitive types
 ---------------
 
@@ -626,7 +631,6 @@ Various
 .. autofunction:: pyderasn.tag_decode
 .. autofunction:: pyderasn.tag_ctxp
 .. autofunction:: pyderasn.tag_ctxc
 .. autofunction:: pyderasn.tag_decode
 .. autofunction:: pyderasn.tag_ctxp
 .. autofunction:: pyderasn.tag_ctxc
-.. autoclass:: pyderasn.Obj
 .. autoclass:: pyderasn.DecodeError
    :members: __init__
 .. autoclass:: pyderasn.NotEnoughData
 .. autoclass:: pyderasn.DecodeError
    :members: __init__
 .. autoclass:: pyderasn.NotEnoughData
@@ -670,9 +674,10 @@ from six.moves import xrange as six_xrange
 try:
     from termcolor import colored
 except ImportError:  # pragma: no cover
 try:
     from termcolor import colored
 except ImportError:  # pragma: no cover
-    def colored(what, *args):
+    def colored(what, *args, **kwargs):
         return what
 
         return what
 
+__version__ = "5.5"
 
 __all__ = (
     "Any",
 
 __all__ = (
     "Any",
@@ -1019,9 +1024,9 @@ def len_decode(data):
 ########################################################################
 
 class AutoAddSlots(type):
 ########################################################################
 
 class AutoAddSlots(type):
-    def __new__(mcs, name, bases, _dict):
+    def __new__(cls, name, bases, _dict):
         _dict["__slots__"] = _dict.get("__slots__", ())
         _dict["__slots__"] = _dict.get("__slots__", ())
-        return type.__new__(mcs, name, bases, _dict)
+        return type.__new__(cls, name, bases, _dict)
 
 
 @add_metaclass(AutoAddSlots)
 
 
 @add_metaclass(AutoAddSlots)
@@ -1095,10 +1100,14 @@ class Obj(object):
 
     @property
     def tlen(self):
 
     @property
     def tlen(self):
+        """See :ref:`decoding`
+        """
         return len(self.tag)
 
     @property
     def tlvlen(self):
         return len(self.tag)
 
     @property
     def tlvlen(self):
+        """See :ref:`decoding`
+        """
         return self.tlen + self.llen + self.vlen
 
     def __str__(self):  # pragma: no cover
         return self.tlen + self.llen + self.vlen
 
     def __str__(self):  # pragma: no cover
@@ -1123,6 +1132,10 @@ class Obj(object):
         raise NotImplementedError()
 
     def encode(self):
         raise NotImplementedError()
 
     def encode(self):
+        """Encode the structure
+
+        :returns: DER representation
+        """
         raw = self._encode()
         if self._expl is None:
             return raw
         raw = self._encode()
         if self._expl is None:
             return raw
@@ -1150,6 +1163,8 @@ class Obj(object):
                          determine if tag satisfies the scheme)
         :param _ctx_immutable: do we need to copy ``ctx`` before using it
         :returns: (Obj, remaining data)
                          determine if tag satisfies the scheme)
         :param _ctx_immutable: do we need to copy ``ctx`` before using it
         :returns: (Obj, remaining data)
+
+        .. seealso:: :ref:`decoding`
         """
         if ctx is None:
             ctx = {}
         """
         if ctx is None:
             ctx = {}
@@ -1165,7 +1180,7 @@ class Obj(object):
                 tag_only=tag_only,
             )
             if tag_only:
                 tag_only=tag_only,
             )
             if tag_only:
-                return
+                return None
             obj, tail = result
         else:
             try:
             obj, tail = result
         else:
             try:
@@ -1203,7 +1218,7 @@ class Obj(object):
                     tag_only=tag_only,
                 )
                 if tag_only:  # pragma: no cover
                     tag_only=tag_only,
                 )
                 if tag_only:  # pragma: no cover
-                    return
+                    return None
                 obj, tail = result
                 eoc_expected, tail = tail[:EOC_LEN], tail[EOC_LEN:]
                 if eoc_expected.tobytes() != EOC:
                 obj, tail = result
                 eoc_expected, tail = tail[:EOC_LEN], tail[EOC_LEN:]
                 if eoc_expected.tobytes() != EOC:
@@ -1238,7 +1253,7 @@ class Obj(object):
                     tag_only=tag_only,
                 )
                 if tag_only:  # pragma: no cover
                     tag_only=tag_only,
                 )
                 if tag_only:  # pragma: no cover
-                    return
+                    return None
                 obj, tail = result
                 if obj.tlvlen < l and not ctx.get("allow_expl_oob", False):
                     raise DecodeError(
                 obj, tail = result
                 if obj.tlvlen < l and not ctx.get("allow_expl_oob", False):
                     raise DecodeError(
@@ -1251,40 +1266,58 @@ class Obj(object):
 
     @property
     def expled(self):
 
     @property
     def expled(self):
+        """See :ref:`decoding`
+        """
         return self._expl is not None
 
     @property
     def expl_tag(self):
         return self._expl is not None
 
     @property
     def expl_tag(self):
+        """See :ref:`decoding`
+        """
         return self._expl
 
     @property
     def expl_tlen(self):
         return self._expl
 
     @property
     def expl_tlen(self):
+        """See :ref:`decoding`
+        """
         return len(self._expl)
 
     @property
     def expl_llen(self):
         return len(self._expl)
 
     @property
     def expl_llen(self):
+        """See :ref:`decoding`
+        """
         if self.expl_lenindef:
             return 1
         return len(len_encode(self.tlvlen))
 
     @property
     def expl_offset(self):
         if self.expl_lenindef:
             return 1
         return len(len_encode(self.tlvlen))
 
     @property
     def expl_offset(self):
+        """See :ref:`decoding`
+        """
         return self.offset - self.expl_tlen - self.expl_llen
 
     @property
     def expl_vlen(self):
         return self.offset - self.expl_tlen - self.expl_llen
 
     @property
     def expl_vlen(self):
+        """See :ref:`decoding`
+        """
         return self.tlvlen
 
     @property
     def expl_tlvlen(self):
         return self.tlvlen
 
     @property
     def expl_tlvlen(self):
+        """See :ref:`decoding`
+        """
         return self.expl_tlen + self.expl_llen + self.expl_vlen
 
     @property
     def fulloffset(self):
         return self.expl_tlen + self.expl_llen + self.expl_vlen
 
     @property
     def fulloffset(self):
+        """See :ref:`decoding`
+        """
         return self.expl_offset if self.expled else self.offset
 
     @property
     def fulllen(self):
         return self.expl_offset if self.expled else self.offset
 
     @property
     def fulllen(self):
+        """See :ref:`decoding`
+        """
         return self.expl_tlvlen if self.expled else self.tlvlen
 
     def pps_lenindef(self, decode_path):
         return self.expl_tlvlen if self.expled else self.tlvlen
 
     def pps_lenindef(self, decode_path):
@@ -1435,7 +1468,7 @@ def colonize_hex(hexed):
 
 def pp_console_row(
         pp,
 
 def pp_console_row(
         pp,
-        oids=None,
+        oid_maps=(),
         with_offsets=False,
         with_blob=True,
         with_colours=False,
         with_offsets=False,
         with_blob=True,
         with_colours=False,
@@ -1470,14 +1503,18 @@ def pp_console_row(
         if isinstance(ent, DecodePathDefBy):
             cols.append(_colourize("DEFINED BY", "red", with_colours, ("reverse",)))
             value = str(ent.defined_by)
         if isinstance(ent, DecodePathDefBy):
             cols.append(_colourize("DEFINED BY", "red", with_colours, ("reverse",)))
             value = str(ent.defined_by)
+            oid_name = None
             if (
             if (
-                    oids is not None and
+                    len(oid_maps) > 0 and
                     ent.defined_by.asn1_type_name ==
                     ent.defined_by.asn1_type_name ==
-                    ObjectIdentifier.asn1_type_name and
-                    value in oids
+                    ObjectIdentifier.asn1_type_name
             ):
             ):
-                cols.append(_colourize("%s:" % oids[value], "green", with_colours))
-            else:
+                for oid_map in oid_maps:
+                    oid_name = oid_map.get(value)
+                    if oid_name is not None:
+                        cols.append(_colourize("%s:" % oid_name, "green", with_colours))
+                        break
+            if oid_name is None:
                 cols.append(_colourize("%s:" % value, "white", with_colours, ("reverse",)))
         else:
             cols.append(_colourize("%s:" % ent, "yellow", with_colours, ("reverse",)))
                 cols.append(_colourize("%s:" % value, "white", with_colours, ("reverse",)))
         else:
             cols.append(_colourize("%s:" % ent, "yellow", with_colours, ("reverse",)))
@@ -1498,11 +1535,14 @@ def pp_console_row(
         value = pp.value
         cols.append(_colourize(value, "white", with_colours, ("reverse",)))
         if (
         value = pp.value
         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
+                len(oid_maps) > 0 and
+                pp.asn1_type_name == ObjectIdentifier.asn1_type_name
         ):
         ):
-            cols.append(_colourize("(%s)" % oids[value], "green", with_colours))
+            for oid_map in oid_maps:
+                oid_name = oid_map.get(value)
+                if oid_name is not None:
+                    cols.append(_colourize("(%s)" % oid_name, "green", with_colours))
+                    break
         if pp.asn1_type_name == Integer.asn1_type_name:
             hex_repr = hex(int(pp.obj._value))[2:].upper()
             if len(hex_repr) % 2 != 0:
         if pp.asn1_type_name == Integer.asn1_type_name:
             hex_repr = hex(int(pp.obj._value))[2:].upper()
             if len(hex_repr) % 2 != 0:
@@ -1546,7 +1586,7 @@ def pp_console_blob(pp, decode_path_len_decrease=0):
 
 def pprint(
         obj,
 
 def pprint(
         obj,
-        oids=None,
+        oid_maps=(),
         big_blobs=False,
         with_colours=False,
         with_decode_path=False,
         big_blobs=False,
         with_colours=False,
         with_decode_path=False,
@@ -1555,8 +1595,9 @@ def pprint(
     """Pretty print object
 
     :param Obj obj: object you want to pretty print
     """Pretty print object
 
     :param Obj obj: object you want to pretty print
-    :param oids: ``OID <-> humand readable string`` dictionary. When OID
-                 from it is met, then its humand readable form is printed
+    :param oid_maps: list of ``OID <-> humand readable string`` dictionary.
+                     When OID from it is met, then its humand readable form
+                     is printed
     :param big_blobs: if large binary objects are met (like OctetString
                       values), do we need to print them too, on separate
                       lines
     :param big_blobs: if large binary objects are met (like OctetString
                       values), do we need to print them too, on separate
                       lines
@@ -1578,7 +1619,7 @@ def pprint(
                 if big_blobs:
                     yield pp_console_row(
                         pp,
                 if big_blobs:
                     yield pp_console_row(
                         pp,
-                        oids=oids,
+                        oid_maps=oid_maps,
                         with_offsets=True,
                         with_blob=False,
                         with_colours=with_colours,
                         with_offsets=True,
                         with_blob=False,
                         with_colours=with_colours,
@@ -1593,7 +1634,7 @@ def pprint(
                 else:
                     yield pp_console_row(
                         pp,
                 else:
                     yield pp_console_row(
                         pp,
-                        oids=oids,
+                        oid_maps=oid_maps,
                         with_offsets=True,
                         with_blob=True,
                         with_colours=with_colours,
                         with_offsets=True,
                         with_blob=True,
                         with_colours=with_colours,
@@ -1739,7 +1780,7 @@ class Boolean(Obj):
                 offset=offset,
             )
         if tag_only:
                 offset=offset,
             )
         if tag_only:
-            return
+            return None
         try:
             l, _, v = len_decode(lv)
         except DecodeError as err:
         try:
             l, _, v = len_decode(lv)
         except DecodeError as err:
@@ -1969,6 +2010,7 @@ class Integer(Obj):
         for name, value in iteritems(self.specs):
             if value == self._value:
                 return name
         for name, value in iteritems(self.specs):
             if value == self._value:
                 return name
+        return None
 
     def __call__(
             self,
 
     def __call__(
             self,
@@ -2048,7 +2090,7 @@ class Integer(Obj):
                 offset=offset,
             )
         if tag_only:
                 offset=offset,
             )
         if tag_only:
-            return
+            return None
         try:
             l, llen, v = len_decode(lv)
         except DecodeError as err:
         try:
             l, llen, v = len_decode(lv)
         except DecodeError as err:
@@ -2466,7 +2508,7 @@ class BitString(Obj):
             )
         if t == self.tag:
             if tag_only:  # pragma: no cover
             )
         if t == self.tag:
             if tag_only:  # pragma: no cover
-                return
+                return None
             return self._decode_chunk(lv, offset, decode_path, ctx)
         if t == self.tag_constructed:
             if not ctx.get("bered", False):
             return self._decode_chunk(lv, offset, decode_path, ctx)
         if t == self.tag_constructed:
             if not ctx.get("bered", False):
@@ -2477,7 +2519,7 @@ class BitString(Obj):
                     offset=offset,
                 )
             if tag_only:  # pragma: no cover
                     offset=offset,
                 )
             if tag_only:  # pragma: no cover
-                return
+                return None
             lenindef = False
             try:
                 l, llen, v = len_decode(lv)
             lenindef = False
             try:
                 l, llen, v = len_decode(lv)
@@ -2842,7 +2884,7 @@ class OctetString(Obj):
             )
         if t == self.tag:
             if tag_only:
             )
         if t == self.tag:
             if tag_only:
-                return
+                return None
             return self._decode_chunk(lv, offset, decode_path, ctx)
         if t == self.tag_constructed:
             if not ctx.get("bered", False):
             return self._decode_chunk(lv, offset, decode_path, ctx)
         if t == self.tag_constructed:
             if not ctx.get("bered", False):
@@ -2853,7 +2895,7 @@ class OctetString(Obj):
                     offset=offset,
                 )
             if tag_only:
                     offset=offset,
                 )
             if tag_only:
-                return
+                return None
             lenindef = False
             try:
                 l, llen, v = len_decode(lv)
             lenindef = False
             try:
                 l, llen, v = len_decode(lv)
@@ -3069,7 +3111,7 @@ class Null(Obj):
                 offset=offset,
             )
         if tag_only:  # pragma: no cover
                 offset=offset,
             )
         if tag_only:  # pragma: no cover
-            return
+            return None
         try:
             l, _, v = len_decode(lv)
         except DecodeError as err:
         try:
             l, _, v = len_decode(lv)
         except DecodeError as err:
@@ -3324,7 +3366,7 @@ class ObjectIdentifier(Obj):
                 offset=offset,
             )
         if tag_only:  # pragma: no cover
                 offset=offset,
             )
         if tag_only:  # pragma: no cover
-            return
+            return None
         try:
             l, llen, v = len_decode(lv)
         except DecodeError as err:
         try:
             l, llen, v = len_decode(lv)
         except DecodeError as err:
@@ -4242,7 +4284,7 @@ class Choice(Obj):
                 offset=offset,
             )
         if tag_only:  # pragma: no cover
                 offset=offset,
             )
         if tag_only:  # pragma: no cover
-            return
+            return None
         value, tail = spec.decode(
             tlv,
             offset=offset,
         value, tail = spec.decode(
             tlv,
             offset=offset,
@@ -4825,7 +4867,7 @@ class Sequence(Obj):
                 offset=offset,
             )
         if tag_only:  # pragma: no cover
                 offset=offset,
             )
         if tag_only:  # pragma: no cover
-            return
+            return None
         lenindef = False
         ctx_bered = ctx.get("bered", False)
         try:
         lenindef = False
         ctx_bered = ctx.get("bered", False)
         try:
@@ -4877,8 +4919,8 @@ class Sequence(Obj):
                     ctx=ctx,
                     _ctx_immutable=False,
                 )
                     ctx=ctx,
                     _ctx_immutable=False,
                 )
-            except TagMismatch:
-                if spec.optional:
+            except TagMismatch as err:
+                if (len(err.decode_path) == len(decode_path) + 1) and spec.optional:
                     continue
                 raise
 
                     continue
                 raise
 
@@ -5075,7 +5117,7 @@ class Set(Sequence):
                 offset=offset,
             )
         if tag_only:
                 offset=offset,
             )
         if tag_only:
-            return
+            return None
         lenindef = False
         ctx_bered = ctx.get("bered", False)
         try:
         lenindef = False
         ctx_bered = ctx.get("bered", False)
         try:
@@ -5408,7 +5450,7 @@ class SequenceOf(Obj):
                 offset=offset,
             )
         if tag_only:
                 offset=offset,
             )
         if tag_only:
-            return
+            return None
         lenindef = False
         ctx_bered = ctx.get("bered", False)
         try:
         lenindef = False
         ctx_bered = ctx.get("bered", False)
         try:
@@ -5604,7 +5646,7 @@ def generic_decoder():  # pragma: no cover
 
     def pprint_any(
             obj,
 
     def pprint_any(
             obj,
-            oids=None,
+            oid_maps=(),
             with_colours=False,
             with_decode_path=False,
             decode_path_only=(),
             with_colours=False,
             with_decode_path=False,
             decode_path_only=(),
@@ -5624,7 +5666,7 @@ def generic_decoder():  # pragma: no cover
                     pp = _pp(**pp_kwargs)
                     yield pp_console_row(
                         pp,
                     pp = _pp(**pp_kwargs)
                     yield pp_console_row(
                         pp,
-                        oids=oids,
+                        oid_maps=oid_maps,
                         with_offsets=True,
                         with_blob=False,
                         with_colours=with_colours,
                         with_offsets=True,
                         with_blob=False,
                         with_colours=with_colours,
@@ -5654,7 +5696,7 @@ def main():  # pragma: no cover
     )
     parser.add_argument(
         "--oids",
     )
     parser.add_argument(
         "--oids",
-        help="Python path to dictionary with OIDs",
+        help="Python paths to dictionary with OIDs, comma separated",
     )
     parser.add_argument(
         "--schema",
     )
     parser.add_argument(
         "--schema",
@@ -5692,7 +5734,10 @@ def main():  # pragma: no cover
     args.DERFile.seek(args.skip)
     der = memoryview(args.DERFile.read())
     args.DERFile.close()
     args.DERFile.seek(args.skip)
     der = memoryview(args.DERFile.read())
     args.DERFile.close()
-    oids = obj_by_path(args.oids) if args.oids else {}
+    oid_maps = (
+        [obj_by_path(_path) for _path in (args.oids or "").split(",")]
+        if args.oids else ()
+    )
     if args.schema:
         schema = obj_by_path(args.schema)
         from functools import partial
     if args.schema:
         schema = obj_by_path(args.schema)
         from functools import partial
@@ -5708,7 +5753,7 @@ def main():  # pragma: no cover
     obj, tail = schema().decode(der, ctx=ctx)
     print(pprinter(
         obj,
     obj, tail = schema().decode(der, ctx=ctx)
     print(pprinter(
         obj,
-        oids=oids,
+        oid_maps=oid_maps,
         with_colours=True if environ.get("NO_COLOR") is None else False,
         with_decode_path=args.print_decode_path,
         decode_path_only=(
         with_colours=True if environ.get("NO_COLOR") is None else False,
         with_decode_path=args.print_decode_path,
         decode_path_only=(