]> Cypherpunks.ru repositories - pyderasn.git/blobdiff - pyderasn.py
Check that explicit tag is not out-of-bounds
[pyderasn.git] / pyderasn.py
index 3f0e8caec4e8e33ca804dd34d1cf95656fe50d97..6db8017e12a60a7d7ddfa4202514c31f808bd39e 100755 (executable)
@@ -213,6 +213,7 @@ decoding process.
 
 Currently available context options:
 
+* :ref:`allow_expl_oob <allow_expl_oob_ctx>`
 * :ref:`bered <bered_ctx>`
 * :ref:`defines_by_path <defines_by_path_ctx>`
 * :ref:`strict_default_existence <strict_default_existence_ctx>`
@@ -274,7 +275,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",), {
@@ -302,8 +303,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:
 
@@ -394,6 +394,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
 ---------------
 
@@ -641,7 +657,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 "",
@@ -1093,6 +1109,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
@@ -1268,6 +1291,8 @@ 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:
@@ -1288,8 +1313,9 @@ def pp_console_row(
         )
         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(_colourize("DEFINED BY", "red", with_colours, ("reverse",)))
@@ -1336,13 +1362,20 @@ def pp_console_row(
         cols.append(_colourize("OPTIONAL", "red", with_colours))
     if pp.default:
         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):
+def pp_console_blob(pp, decode_path_len_decrease=0):
     cols = [" " * len("XXXXXYYZ [X,X,XXXX]Z")]
-    if len(pp.decode_path) > 0:
-        cols.append(" ." * (len(pp.decode_path) + 1))
+    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):
@@ -1354,7 +1387,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
@@ -1365,10 +1405,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,
@@ -1376,8 +1425,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(
@@ -1386,6 +1440,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):
@@ -5138,10 +5194,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()
@@ -5153,8 +5220,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):
@@ -5186,9 +5258,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"),
@@ -5205,7 +5291,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)
@@ -5213,6 +5302,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))