X-Git-Url: http://www.git.cypherpunks.ru/?p=pyderasn.git;a=blobdiff_plain;f=pyderasn.py;h=386f234ae4b7adf62cd2ed446ab84f71c14ce57f;hp=acf0e469260588a921f20d5493546a86955e2a2b;hb=54876436a23f14951f2e6353e9072c9a098b35b4;hpb=4d56972efc2c9afe2edb27c4d91bf5c1d5ca4ede diff --git a/pyderasn.py b/pyderasn.py index acf0e46..386f234 100755 --- a/pyderasn.py +++ b/pyderasn.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # coding: utf-8 # PyDERASN -- Python ASN.1 DER codec with abstract structures -# Copyright (C) 2017 Sergey Matveev +# Copyright (C) 2017-2018 Sergey Matveev # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as @@ -195,6 +195,20 @@ lesser than ``offset``), ``expl_tlen``, ``expl_llen``, ``expl_vlen`` When error occurs, then :py:exc:`pyderasn.DecodeError` is raised. +.. _ctx: + +Context +_______ + +You can specify so called context keyword argument during ``decode()`` +invocation. It is dictionary containing various options governing +decoding process. + +Currently available context options: + +* :ref:`defines_by_path ` +* :ref:`strict_default_existence ` + .. _pprinting: Pretty printing @@ -230,15 +244,15 @@ _____________ :py:class:`pyderasn.ObjectIdentifier` field inside :py:class:`pyderasn.Sequence` can hold mapping between OIDs and -necessary for decoding structrures. For example, CMS (:rfc:`5652`) +necessary for decoding structures. For example, CMS (:rfc:`5652`) container:: class ContentInfo(Sequence): schema = ( - ("contentType", ContentType(defines=("content", { + ("contentType", ContentType(defines=((("content",), { id_digestedData: DigestedData(), id_signedData: SignedData(), - }))), + }),))), ("content", Any(expl=tag_ctxc(0))), ) @@ -248,6 +262,27 @@ decoded with ``SignedData`` specification, if ``contentType`` equals to ``contentType`` contains unknown OID, then no automatic decoding is done. +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``:: + + ( + (('parameters',), { + id_ecPublicKey: ECParameters(), + id_GostR3410_2001: GostR34102001PublicKeyParameters(), + }), + (('..', 'subjectPublicKey'), { + id_rsaEncryption: RSAPublicKey(), + id_GostR3410_2001: OctetString(), + }), + ), + +tells that if certificate's SPKI algorithm is GOST R 34.10-2001, then +autodecode its parameters inside SPKI's algorithm and its public key +itself. + Following types can be automatically decoded (DEFINED BY): * :py:class:`pyderasn.Any` @@ -262,17 +297,17 @@ was defined, ``value`` contains corresponding decoded value. For example above, ``content_info["content"].defined == (id_signedData, signed_data)``. -.. _defines_by_path_kwarg: +.. _defines_by_path_ctx: -defines_by_path kwarg -_____________________ +defines_by_path context option +______________________________ Sometimes you either can not or do not want to explicitly set *defines* in the scheme. You can dynamically apply those definitions when calling ``.decode()`` method. -Decode method takes optional ``defines_by_path`` keyword argument that -must be sequence of following tuples:: +Specify ``defines_by_path`` key in the :ref:`decode context `. Its +value must be sequence of following tuples:: (decode_path, defines) @@ -289,41 +324,41 @@ of ``PKIResponse``:: content_info, tail = ContentInfo().decode(data, defines_by_path=( ( ("contentType",), - ("content", {id_signedData: SignedData()}), + ((("content",), {id_signedData: SignedData()}),), ), ( ( "content", - decode_path_defby(id_signedData), + DecodePathDefBy(id_signedData), "encapContentInfo", "eContentType", ), - ("eContent", { + ((("eContent",), { id_cct_PKIData: PKIData(), id_cct_PKIResponse: PKIResponse(), - }), + })), ), ( ( "content", - decode_path_defby(id_signedData), + DecodePathDefBy(id_signedData), "encapContentInfo", "eContent", - decode_path_defby(id_cct_PKIResponse), + DecodePathDefBy(id_cct_PKIResponse), "controlSequence", any, "attrType", ), - ("attrValues", { + ((("attrValues",), { id_cmc_recipientNonce: RecipientNonce(), id_cmc_senderNonce: SenderNonce(), id_cmc_statusInfoV2: CMCStatusInfoV2(), id_cmc_transactionId: TransactionId(), - }), + })), ), )) -Pay attention for :py:func:`pyderasn.decode_path_defby` and ``any``. +Pay attention for :py:class:`pyderasn.DecodePathDefBy` and ``any``. 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. @@ -421,6 +456,7 @@ _____ Various ------- +.. autofunction:: pyderasn.abs_decode_path .. autofunction:: pyderasn.hexenc .. autofunction:: pyderasn.hexdec .. autofunction:: pyderasn.tag_encode @@ -436,6 +472,7 @@ from collections import namedtuple from collections import OrderedDict from datetime import datetime from math import ceil +from os import environ from six import add_metaclass from six import binary_type @@ -450,6 +487,13 @@ from six import text_type from six.moves import xrange as six_xrange +try: + from termcolor import colored +except ImportError: + def colored(what, *args): + return what + + __all__ = ( "Any", "BitString", @@ -457,8 +501,8 @@ __all__ = ( "Boolean", "BoundsError", "Choice", - "decode_path_defby", "DecodeError", + "DecodePathDefBy", "Enumerated", "GeneralizedTime", "GeneralString", @@ -545,7 +589,7 @@ class DecodeError(Exception): c for c in ( "" if self.klass is None else self.klass.__name__, ( - ("(%s)" % ".".join(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 "", @@ -777,9 +821,9 @@ def len_decode(data): ######################################################################## class AutoAddSlots(type): - def __new__(cls, name, bases, _dict): + def __new__(mcs, name, bases, _dict): _dict["__slots__"] = _dict.get("__slots__", ()) - return type.__new__(cls, name, bases, _dict) + return type.__new__(mcs, name, bases, _dict) @add_metaclass(AutoAddSlots) @@ -867,7 +911,7 @@ class Obj(object): def _encode(self): # pragma: no cover raise NotImplementedError() - def _decode(self, tlv, offset=0, decode_path=(), defines_by_path=None): # pragma: no cover + def _decode(self, tlv, offset, decode_path, ctx): # pragma: no cover raise NotImplementedError() def encode(self): @@ -876,23 +920,25 @@ class Obj(object): return raw return b"".join((self._expl, len_encode(len(raw)), raw)) - def decode(self, data, offset=0, leavemm=False, decode_path=(), defines_by_path=None): + def decode(self, data, offset=0, leavemm=False, decode_path=(), ctx=None): """Decode the data :param data: either binary or memoryview :param int offset: initial data's offset :param bool leavemm: do we need to leave memoryview of remaining data as is, or convert it to bytes otherwise - :param defines_by_path: :ref:`Read about DEFINED BY ` + :param ctx: optional :ref:`context ` governing decoding process. :returns: (Obj, remaining data) """ + if ctx is None: + ctx = {} tlv = memoryview(data) if self._expl is None: obj, tail = self._decode( tlv, offset, decode_path=decode_path, - defines_by_path=defines_by_path, + ctx=ctx, ) else: try: @@ -930,7 +976,7 @@ class Obj(object): v, offset=offset + tlen + llen, decode_path=decode_path, - defines_by_path=defines_by_path, + ctx=ctx, ) return obj, (tail if leavemm else tail.tobytes()) @@ -963,10 +1009,24 @@ class Obj(object): return self.expl_tlen + self.expl_llen + self.expl_vlen -def decode_path_defby(defined_by): +class DecodePathDefBy(object): """DEFINED BY representation inside decode path """ - return "DEFINED BY (%s)" % defined_by + __slots__ = ('defined_by',) + + def __init__(self, defined_by): + self.defined_by = defined_by + + def __eq__(self, their): + if not isinstance(their, self.__class__): + return False + return self.defined_by == their.defined_by + + def __str__(self): + return "DEFINED BY " + str(self.defined_by) + + def __repr__(self): + return "<%s: %s>" % (self.__class__.__name__, self.defined_by) ######################################################################## @@ -1034,49 +1094,75 @@ def _pp( ) -def pp_console_row(pp, oids=None, with_offsets=False, with_blob=True): +def _colorize(what, colour, with_colours, attrs=("bold",)): + return colored(what, colour, attrs=attrs) if with_colours else what + + +def pp_console_row( + pp, + oids=None, + with_offsets=False, + with_blob=True, + with_colours=False, +): cols = [] if with_offsets: - cols.append("%5d%s [%d,%d,%4d]" % ( + col = "%5d%s" % ( pp.offset, ( " " if pp.expl_offset is None else ("-%d" % (pp.offset - pp.expl_offset)) ), - pp.tlen, - pp.llen, - pp.vlen, - )) + ) + cols.append(_colorize(col, "red", with_colours, ())) + col = "[%d,%d,%4d]" % (pp.tlen, pp.llen, pp.vlen) + cols.append(_colorize(col, "green", with_colours, ())) if len(pp.decode_path) > 0: cols.append(" ." * (len(pp.decode_path))) - cols.append("%s:" % pp.decode_path[-1]) + ent = pp.decode_path[-1] + if isinstance(ent, DecodePathDefBy): + cols.append(_colorize("DEFINED BY", "red", with_colours, ("reverse",))) + value = str(ent.defined_by) + if ( + oids is not None and + ent.defined_by.asn1_type_name == + ObjectIdentifier.asn1_type_name and + value in oids + ): + cols.append(_colorize("%s:" % oids[value], "green", with_colours)) + else: + cols.append(_colorize("%s:" % value, "white", with_colours)) + else: + cols.append(_colorize("%s:" % ent, "yellow", with_colours)) if pp.expl is not None: klass, _, num = pp.expl - cols.append("[%s%d] EXPLICIT" % (TagClassReprs[klass], num)) + col = "[%s%d] EXPLICIT" % (TagClassReprs[klass], num) + cols.append(_colorize(col, "blue", with_colours)) if pp.impl is not None: klass, _, num = pp.impl - cols.append("[%s%d]" % (TagClassReprs[klass], num)) + col = "[%s%d]" % (TagClassReprs[klass], num) + cols.append(_colorize(col, "blue", with_colours)) if pp.asn1_type_name.replace(" ", "") != pp.obj_name.upper(): - cols.append(pp.obj_name) - cols.append(pp.asn1_type_name) + cols.append(_colorize(pp.obj_name, "magenta", with_colours)) + cols.append(_colorize(pp.asn1_type_name, "cyan", with_colours)) if pp.value is not None: value = pp.value + cols.append(_colorize(value, "white", with_colours)) if ( oids is not None and pp.asn1_type_name == ObjectIdentifier.asn1_type_name and value in oids ): - value = "%s (%s)" % (oids[value], pp.value) - cols.append(value) + cols.append(_colorize("(%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("OPTIONAL") + cols.append(_colorize("OPTIONAL", "red", with_colours)) if pp.default: - cols.append("DEFAULT") + cols.append(_colorize("DEFAULT", "red", with_colours)) return " ".join(cols) @@ -1095,7 +1181,7 @@ def pp_console_blob(pp): yield " ".join(cols + [", ".join(pp.blob)]) -def pprint(obj, oids=None, big_blobs=False): +def pprint(obj, oids=None, big_blobs=False, with_colours=False): """Pretty print object :param Obj obj: object you want to pretty print @@ -1104,6 +1190,8 @@ def pprint(obj, oids=None, big_blobs=False): :param big_blobs: if large binary objects are met (like OctetString values), do we need to print them too, on separate lines + :param with_colours: colourize output, if ``termcolor`` library + is available """ def _pprint_pps(pps): for pp in pps: @@ -1114,11 +1202,18 @@ def pprint(obj, oids=None, big_blobs=False): oids=oids, with_offsets=True, with_blob=False, + with_colours=with_colours, ) for row in pp_console_blob(pp): yield row else: - yield pp_console_row(pp, oids=oids, with_offsets=True) + yield pp_console_row( + pp, + oids=oids, + with_offsets=True, + with_blob=True, + with_colours=with_colours, + ) else: for row in _pprint_pps(pp): yield row @@ -1238,7 +1333,7 @@ class Boolean(Obj): (b"\xFF" if self._value else b"\x00"), )) - def _decode(self, tlv, offset=0, decode_path=(), defines_by_path=None): + def _decode(self, tlv, offset, decode_path, ctx): try: t, _, lv = tag_strip(tlv) except DecodeError as err: @@ -1531,7 +1626,7 @@ class Integer(Obj): break return b"".join((self.tag, len_encode(len(octets)), octets)) - def _decode(self, tlv, offset=0, decode_path=(), defines_by_path=None): + def _decode(self, tlv, offset, decode_path, ctx): try: t, _, lv = tag_strip(tlv) except DecodeError as err: @@ -1865,7 +1960,7 @@ class BitString(Obj): octets, )) - def _decode(self, tlv, offset=0, decode_path=(), defines_by_path=None): + def _decode(self, tlv, offset, decode_path, ctx): try: t, _, lv = tag_strip(tlv) except DecodeError as err: @@ -1971,7 +2066,7 @@ class BitString(Obj): defined_by, defined = self.defined or (None, None) if defined_by is not None: yield defined.pps( - decode_path=decode_path + (decode_path_defby(defined_by),) + decode_path=decode_path + (DecodePathDefBy(defined_by),) ) @@ -2117,7 +2212,7 @@ class OctetString(Obj): self._value, )) - def _decode(self, tlv, offset=0, decode_path=(), defines_by_path=None): + def _decode(self, tlv, offset, decode_path, ctx): try: t, _, lv = tag_strip(tlv) except DecodeError as err: @@ -2195,7 +2290,7 @@ class OctetString(Obj): defined_by, defined = self.defined or (None, None) if defined_by is not None: yield defined.pps( - decode_path=decode_path + (decode_path_defby(defined_by),) + decode_path=decode_path + (DecodePathDefBy(defined_by),) ) @@ -2266,7 +2361,7 @@ class Null(Obj): def _encode(self): return self.tag + len_encode(0) - def _decode(self, tlv, offset=0, decode_path=(), defines_by_path=None): + def _decode(self, tlv, offset, decode_path, ctx): try: t, _, lv = tag_strip(tlv) except DecodeError as err: @@ -2353,7 +2448,7 @@ class ObjectIdentifier(Obj): def __init__( self, value=None, - defines=None, + defines=(), impl=None, expl=None, default=None, @@ -2364,12 +2459,14 @@ class ObjectIdentifier(Obj): :param value: set the value. Either tuples of integers, string of "."-concatenated integers, or :py:class:`pyderasn.ObjectIdentifier` object - :param defines: tuple of two elements. First one is a name of - field inside :py:class:`pyderasn.Sequence`, - defining with that OID. Second element is a - ``{OID: pyderasn.Obj()}`` dictionary, mapping - between current OID value and structure applied - to defined field. + :param defines: sequence of tuples. Each tuple has two elements. + First one is relative to current one decode + path, aiming to the field defined by that OID. + Read about relative path in + :py:func:`pyderasn.abs_decode_path`. Second + tuple element is ``{OID: pyderasn.Obj()}`` + dictionary, mapping between current OID value + and structure applied to defined field. :ref:`Read about DEFINED BY ` :param bytes impl: override default tag with ``IMPLICIT`` one :param bytes expl: override default tag with ``EXPLICIT`` one @@ -2509,7 +2606,7 @@ class ObjectIdentifier(Obj): v = b"".join(octets) return b"".join((self.tag, len_encode(len(v)), v)) - def _decode(self, tlv, offset=0, decode_path=(), defines_by_path=None): + def _decode(self, tlv, offset, decode_path, ctx): try: t, _, lv = tag_strip(tlv) except DecodeError as err: @@ -3276,7 +3373,7 @@ class Choice(Obj): self._assert_ready() return self._value[1].encode() - def _decode(self, tlv, offset=0, decode_path=(), defines_by_path=None): + def _decode(self, tlv, offset, decode_path, ctx): for choice, spec in self.specs.items(): try: value, tail = spec.decode( @@ -3284,7 +3381,7 @@ class Choice(Obj): offset=offset, leavemm=True, decode_path=decode_path + (choice,), - defines_by_path=defines_by_path, + ctx=ctx, ) except TagMismatch: continue @@ -3451,7 +3548,7 @@ class Any(Obj): self._assert_ready() return self._value - def _decode(self, tlv, offset=0, decode_path=(), defines_by_path=None): + def _decode(self, tlv, offset, decode_path, ctx): try: t, tlen, lv = tag_strip(tlv) l, llen, v = len_decode(lv) @@ -3505,7 +3602,7 @@ class Any(Obj): defined_by, defined = self.defined or (None, None) if defined_by is not None: yield defined.pps( - decode_path=decode_path + (decode_path_defby(defined_by),) + decode_path=decode_path + (DecodePathDefBy(defined_by),) ) @@ -3526,6 +3623,32 @@ def get_def_by_path(defines_by_path, sub_decode_path): return define +def abs_decode_path(decode_path, rel_path): + """Create an absolute decode path from current and relative ones + + :param decode_path: current decode path, starting point. + Tuple of strings + :param rel_path: relative path to ``decode_path``. Tuple of strings. + If first tuple's element is "/", then treat it as + an absolute path, ignoring ``decode_path`` as + starting point. Also this tuple can contain ".." + elements, stripping the leading element from + ``decode_path`` + + >>> abs_decode_path(("foo", "bar"), ("baz", "whatever")) + ("foo", "bar", "baz", "whatever") + >>> abs_decode_path(("foo", "bar", "baz"), ("..", "..", "whatever")) + ("foo", "whatever") + >>> abs_decode_path(("foo", "bar"), ("/", "baz", "whatever")) + ("baz", "whatever") + """ + if rel_path[0] == "/": + return rel_path[1:] + if rel_path[0] == "..": + return abs_decode_path(decode_path[:-1], rel_path[1:]) + return decode_path + rel_path + + class Sequence(Obj): """``SEQUENCE`` structure type @@ -3576,6 +3699,8 @@ class Sequence(Obj): >>> tbs = TBSCertificate() >>> tbs["version"] = Version("v2") # no need to explicitly add ``expl`` + Assign ``None`` to remove value from sequence. + You can know if value exists/set in the sequence and take its value: >>> "extnID" in ext, "extnValue" in ext, "critical" in ext @@ -3595,13 +3720,18 @@ class Sequence(Obj): All defaulted values are always optional. + .. _strict_default_existence_ctx: + .. warning:: When decoded DER contains defaulted value inside, then - technically this is not valid DER encoding. But we allow - and pass it. Of course reencoding of that kind of DER will + 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). + defaulted value inside). You can enable strict defaulted values + existence validation by setting ``"strict_default_existence": + True`` :ref:`context ` option -- decoding process will raise + an exception if defaulted value is met. Two sequences are equal if they have equal specification (schema), implicit/explicit tagging and the same values. @@ -3739,7 +3869,7 @@ class Sequence(Obj): v = b"".join(self._encoded_values()) return b"".join((self.tag, len_encode(len(v)), v)) - def _decode(self, tlv, offset=0, decode_path=(), defines_by_path=None): + def _decode(self, tlv, offset, decode_path, ctx): try: t, tlen, lv = tag_strip(tlv) except DecodeError as err: @@ -3774,7 +3904,6 @@ class Sequence(Obj): v, tail = v[:l], v[l:] sub_offset = offset + tlen + llen values = {} - defines = {} for name, spec in self.specs.items(): if len(v) == 0 and spec.optional: continue @@ -3785,28 +3914,31 @@ class Sequence(Obj): sub_offset, leavemm=True, decode_path=sub_decode_path, - defines_by_path=defines_by_path, + ctx=ctx, ) except TagMismatch: if spec.optional: continue raise - defined = defines.pop(name, None) + 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): for i, _value in enumerate(value): sub_sub_decode_path = sub_decode_path + ( str(i), - decode_path_defby(defined_by), + DecodePathDefBy(defined_by), ) defined_value, defined_tail = defined_spec.decode( memoryview(bytes(_value)), - sub_offset + value.tlen + value.llen, + sub_offset + ( + (value.tlen + value.llen + value.expl_tlen + value.expl_llen) + if value.expled else (value.tlen + value.llen) + ), leavemm=True, decode_path=sub_sub_decode_path, - defines_by_path=defines_by_path, + ctx=ctx, ) if len(defined_tail) > 0: raise DecodeError( @@ -3819,16 +3951,19 @@ class Sequence(Obj): else: defined_value, defined_tail = defined_spec.decode( memoryview(bytes(value)), - sub_offset + value.tlen + value.llen, + sub_offset + ( + (value.tlen + value.llen + value.expl_tlen + value.expl_llen) + if value.expled else (value.tlen + value.llen) + ), leavemm=True, - decode_path=sub_decode_path + (decode_path_defby(defined_by),), - defines_by_path=defines_by_path, + decode_path=sub_decode_path + (DecodePathDefBy(defined_by),), + ctx=ctx, ) if len(defined_tail) > 0: raise DecodeError( "remaining data", klass=self.__class__, - decode_path=sub_decode_path + (decode_path_defby(defined_by),), + decode_path=sub_decode_path + (DecodePathDefBy(defined_by),), offset=offset, ) value.defined = (defined_by, defined_value) @@ -3836,19 +3971,30 @@ class Sequence(Obj): sub_offset += (value.expl_tlvlen if value.expled else value.tlvlen) v = v_tail if spec.default is not None and value == spec.default: - # Encoded default values are not valid in DER, - # but we allow that anyway - continue + if ctx.get("strict_default_existence", False): + 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", None) - if defines_by_path is not None and spec_defines is None: - spec_defines = get_def_by_path(defines_by_path, sub_decode_path) - if spec_defines is not None: - what, schema = spec_defines - defined = schema.get(value, None) - if defined is not None: - defines[what] = (value, defined) + spec_defines = getattr(spec, "defines", ()) + if len(spec_defines) == 0: + defines_by_path = ctx.get("defines_by_path", ()) + if len(defines_by_path) > 0: + spec_defines = get_def_by_path(defines_by_path, sub_decode_path) + if spec_defines is not None and len(spec_defines) > 0: + for rel_path, schema in spec_defines: + defined = schema.get(value, None) + if defined is not None: + ctx.setdefault("defines", []).append(( + abs_decode_path(sub_decode_path[:-1], rel_path), + (value, defined), + )) if len(v) > 0: raise DecodeError( "remaining data", @@ -3917,7 +4063,7 @@ class Set(Sequence): v = b"".join(raws) return b"".join((self.tag, len_encode(len(v)), v)) - def _decode(self, tlv, offset=0, decode_path=(), defines_by_path=None): + def _decode(self, tlv, offset, decode_path, ctx): try: t, tlen, lv = tag_strip(tlv) except DecodeError as err: @@ -3960,7 +4106,7 @@ class Set(Sequence): sub_offset, leavemm=True, decode_path=decode_path + (name,), - defines_by_path=defines_by_path, + ctx=ctx, ) except TagMismatch: continue @@ -4170,7 +4316,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=0, decode_path=(), defines_by_path=None): + def _decode(self, tlv, offset, decode_path, ctx): try: t, tlen, lv = tag_strip(tlv) except DecodeError as err: @@ -4212,7 +4358,7 @@ class SequenceOf(Obj): sub_offset, leavemm=True, decode_path=decode_path + (str(len(_value)),), - defines_by_path=defines_by_path, + ctx=ctx, ) sub_offset += (value.expl_tlvlen if value.expled else value.tlvlen) v = v_tail @@ -4308,7 +4454,7 @@ def generic_decoder(): # pragma: no cover __slots__ = () schema = choice - def pprint_any(obj, oids=None): + def pprint_any(obj, oids=None, with_colours=False): def _pprint_pps(pps): for pp in pps: if hasattr(pp, "_fields"): @@ -4322,6 +4468,7 @@ def generic_decoder(): # pragma: no cover oids=oids, with_offsets=True, with_blob=False, + with_colours=with_colours, ) for row in pp_console_blob(pp): yield row @@ -4371,12 +4518,16 @@ def main(): # pragma: no cover schema, pprinter = generic_decoder() obj, tail = schema().decode( der, - defines_by_path=( - None if args.defines_by_path is None - else obj_by_path(args.defines_by_path) + ctx=( + None if args.defines_by_path is None else + {"defines_by_path": obj_by_path(args.defines_by_path)} ), ) - print(pprinter(obj, oids=oids)) + print(pprinter( + obj, + oids=oids, + with_colours=True if environ.get("NO_COLOR") is None else False, + )) if tail != b"": print("\nTrailing data: %s" % hexenc(tail))