X-Git-Url: http://www.git.cypherpunks.ru/?a=blobdiff_plain;f=pyderasn.py;h=7edfc8064c2c0571404e2449a7906ea745ffd264;hb=cb9ebe2012c619dea2633b00427898312801a840;hp=b28792c9e00e4c8459eff239210d0aaf9794c368;hpb=2486727fb9bf8eaa93bee47ab3a67c038cb6276c;p=pyderasn.git diff --git a/pyderasn.py b/pyderasn.py index b28792c..7edfc80 100755 --- a/pyderasn.py +++ b/pyderasn.py @@ -189,9 +189,16 @@ use following properties: Pay attention that those values do **not** include anything related to explicit tag. If you want to know information about it, then use: -``expled`` (to know if explicit tag is set), ``expl_offset`` (it is -lesser than ``offset``), ``expl_tlen``, ``expl_llen``, ``expl_vlen`` -(that actually equals to ordinary ``tlvlen``). + +* ``expled`` -- to know if explicit tag is set +* ``expl_offset`` (it is lesser than ``offset``) +* ``expl_tlen``, +* ``expl_llen`` +* ``expl_vlen`` (that actually equals to ordinary ``tlvlen``) +* ``fulloffset`` -- it equals to ``expl_offset`` if explicit tag is set, + ``offset`` otherwise +* ``fulllen`` -- it equals to ``expl_len`` if explicit tag is set, + ``tlvlen`` otherwise When error occurs, :py:exc:`pyderasn.DecodeError` is raised. @@ -206,6 +213,7 @@ decoding process. Currently available context options: +* :ref:`allow_expl_oob ` * :ref:`bered ` * :ref:`defines_by_path ` * :ref:`strict_default_existence ` @@ -267,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",), { @@ -295,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: @@ -369,10 +376,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 ` argument to True. Indefinite lengths and @@ -391,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 --------------- @@ -607,6 +626,8 @@ TagClassReprs = { } EOC = b"\x00\x00" EOC_LEN = len(EOC) +LENINDEF = b"\x80" # length indefinite mark +LENINDEF_PP_CHAR = "I" if PY2 else "∞" ######################################################################## @@ -636,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 "", @@ -914,9 +935,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 +1077,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, ) @@ -1089,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 @@ -1121,6 +1148,41 @@ class Obj(object): def expl_tlvlen(self): return self.expl_tlen + self.expl_llen + self.expl_vlen + @property + def fulloffset(self): + return self.expl_offset if self.expled else self.offset + + @property + def fulllen(self): + return self.expl_tlvlen if self.expled else self.tlvlen + + 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 @@ -1219,7 +1281,7 @@ def _pp( ) -def _colorize(what, colour, with_colours, attrs=("bold",)): +def _colourize(what, colour, with_colours, attrs=("bold",)): return colored(what, colour, attrs=attrs) if with_colours else what @@ -1229,34 +1291,34 @@ 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: - 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) + cols.append(_colourize(col, "red", with_colours, ())) + col = "[%d,%d,%4d]%s" % ( + pp.tlen, + pp.llen, + pp.vlen, + LENINDEF_PP_CHAR if pp.lenindef else " " ) + 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(_colorize("DEFINED BY", "red", with_colours, ("reverse",))) + cols.append(_colourize("DEFINED BY", "red", with_colours, ("reverse",))) value = str(ent.defined_by) if ( oids is not None and @@ -1264,49 +1326,56 @@ def pp_console_row( ObjectIdentifier.asn1_type_name and value in oids ): - cols.append(_colorize("%s:" % oids[value], "green", with_colours)) + cols.append(_colourize("%s:" % oids[value], "green", with_colours)) else: - cols.append(_colorize("%s:" % value, "white", with_colours, ("reverse",))) + cols.append(_colourize("%s:" % value, "white", with_colours, ("reverse",))) else: - cols.append(_colorize("%s:" % ent, "yellow", with_colours, ("reverse",))) + cols.append(_colourize("%s:" % ent, "yellow", with_colours, ("reverse",))) if pp.expl is not None: klass, _, num = pp.expl col = "[%s%d] EXPLICIT" % (TagClassReprs[klass], num) - cols.append(_colorize(col, "blue", with_colours)) + cols.append(_colourize(col, "blue", with_colours)) if pp.impl is not None: klass, _, num = pp.impl col = "[%s%d]" % (TagClassReprs[klass], num) - cols.append(_colorize(col, "blue", with_colours)) + cols.append(_colourize(col, "blue", with_colours)) if pp.asn1_type_name.replace(" ", "") != pp.obj_name.upper(): - cols.append(_colorize(pp.obj_name, "magenta", with_colours)) + cols.append(_colourize(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)) + cols.append(_colourize("BER", "red", with_colours)) + cols.append(_colourize(pp.asn1_type_name, "cyan", with_colours)) if pp.value is not None: value = pp.value - cols.append(_colorize(value, "white", with_colours, ("reverse",))) + 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 ): - cols.append(_colorize("(%s)" % oids[value], "green", with_colours)) + cols.append(_colourize("(%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(_colorize("OPTIONAL", "red", with_colours)) + cols.append(_colourize("OPTIONAL", "red", with_colours)) if pp.default: - cols.append(_colorize("DEFAULT", "red", with_colours)) + 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): - cols = [" " * len("XXXXXYY [X,X,XXXX]YY")] - if len(pp.decode_path) > 0: - cols.append(" ." * (len(pp.decode_path) + 1)) +def pp_console_blob(pp, decode_path_len_decrease=0): + cols = [" " * len("XXXXXYYZ [X,X,XXXX]Z")] + 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): @@ -1318,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 @@ -1329,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, @@ -1340,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( @@ -1350,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): @@ -1562,6 +1654,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 +1979,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 +2023,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 `, then decoder will fail, because + of DER restrictions. """ __slots__ = ("tag_constructed", "specs", "defined") tag_default = tag_encode(3) @@ -2002,9 +2106,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 +2298,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 +2318,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 +2344,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 +2360,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 +2371,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 +2381,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 +2447,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 +2466,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 `, then decoder will fail, because + of DER restrictions. """ __slots__ = ("tag_constructed", "_bound_min", "_bound_max", "defined") tag_default = tag_encode(4) @@ -2551,7 +2668,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 +2688,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 +2707,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 +2723,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 +2732,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 +2796,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 +2930,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 +3221,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 +3446,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): @@ -3463,11 +3578,14 @@ class UTCTime(CommonString): if isinstance(value, datetime): return value.strftime(self.fmt).encode("ascii") if isinstance(value, binary_type): - value_decoded = value.decode("ascii") + try: + value_decoded = value.decode("ascii") + except (UnicodeEncodeError, UnicodeDecodeError) as err: + raise DecodeError("invalid UTCTime encoding") if len(value_decoded) == LEN_YYMMDDHHMMSSZ: try: datetime.strptime(value_decoded, self.fmt) - except ValueError: + except (TypeError, ValueError): raise DecodeError("invalid UTCTime format") return value else: @@ -3530,6 +3648,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): @@ -3559,11 +3679,14 @@ class GeneralizedTime(UTCTime): self.fmt_ms if value.microsecond > 0 else self.fmt ).encode("ascii") if isinstance(value, binary_type): - value_decoded = value.decode("ascii") + try: + value_decoded = value.decode("ascii") + except (UnicodeEncodeError, UnicodeDecodeError) as err: + raise DecodeError("invalid GeneralizedTime encoding") if len(value_decoded) == LEN_YYYYMMDDHHMMSSZ: try: datetime.strptime(value_decoded, self.fmt) - except ValueError: + except (TypeError, ValueError): raise DecodeError( "invalid GeneralizedTime (without ms) format", ) @@ -3571,7 +3694,7 @@ class GeneralizedTime(UTCTime): elif len(value_decoded) >= LEN_YYYYMMDDHHMMSSDMZ: try: datetime.strptime(value_decoded, self.fmt_ms) - except ValueError: + except (TypeError, ValueError): raise DecodeError( "invalid GeneralizedTime (with ms) format", ) @@ -3835,7 +3958,7 @@ class Choice(Obj): expl=self._expl, default=self.default, optional=self.optional, - _decoded=(offset, 0, value.tlvlen), + _decoded=(offset, 0, value.fulllen), ) obj._value = (choice, value) return obj, tail @@ -3864,6 +3987,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 +4137,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 +4212,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 +4317,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: @@ -4435,7 +4560,7 @@ class Sequence(Obj): continue raise - defined = get_def_by_path(ctx.get("defines", ()), sub_decode_path) + 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): @@ -4482,7 +4607,7 @@ class Sequence(Obj): ) value.defined = (defined_by, defined_value) - value_len = value.expl_tlvlen if value.expled else value.tlvlen + value_len = value.fulllen vlen += value_len sub_offset += value_len v = v_tail @@ -4507,7 +4632,7 @@ class Sequence(Obj): for rel_path, schema in spec_defines: defined = schema.get(value, None) if defined is not None: - ctx.setdefault("defines", []).append(( + ctx.setdefault("_defines", []).append(( abs_decode_path(sub_decode_path[:-1], rel_path), (value, defined), )) @@ -4547,8 +4672,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 +4700,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): @@ -4672,13 +4799,22 @@ class Set(Sequence): decode_path=sub_decode_path, ctx=ctx, ) - value_len = value.expl_tlvlen if value.expled else value.tlvlen + value_len = value.fulllen 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 + if spec.default is None: values[name] = value + else: + if value != spec.default: + values[name] = value + if ctx.get("strict_default_existence", False): + raise DecodeError( + "DEFAULT value met", + klass=self.__class__, + decode_path=sub_decode_path, + offset=sub_offset, + ) obj = self.__class__( schema=self.specs, impl=self.tag, @@ -4688,15 +4824,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): @@ -4940,23 +5085,40 @@ class SequenceOf(Obj): decode_path=decode_path + (str(len(_value)),), ctx=ctx, ) - value_len = value.expl_tlvlen if value.expled else value.tlvlen + value_len = value.fulllen sub_offset += value_len vlen += value_len v = v_tail _value.append(value) - obj = self.__class__( - value=_value, - schema=spec, - bounds=(self._bound_min, self._bound_max), - impl=self.tag, - expl=self._expl, - default=self.default, - optional=self.optional, - _decoded=(offset, llen, vlen), - ) - obj.lenindef = lenindef - return obj, (v[EOC_LEN:] if lenindef else tail) + try: + obj = self.__class__( + value=_value, + schema=spec, + bounds=(self._bound_min, self._bound_max), + impl=self.tag, + expl=self._expl, + default=self.default, + optional=self.optional, + _decoded=(offset, llen, vlen + (EOC_LEN if lenindef else 0)), + ) + except BoundsError as err: + raise DecodeError( + msg=str(err), + klass=self.__class__, + decode_path=decode_path, + offset=offset, + ) + 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 +5148,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): @@ -5039,10 +5203,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() @@ -5054,8 +5229,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): @@ -5087,9 +5267,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"), @@ -5106,7 +5300,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) @@ -5114,6 +5311,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))