X-Git-Url: http://www.git.cypherpunks.ru/?a=blobdiff_plain;f=pyderasn.py;h=5e2dd2b5163542a940755d1ac16625ced66ab1d4;hb=fda5cb9cfd5c69b1ae3373a22d27d1b5afffa353;hp=6e772e09b5870c743b5c9228d3027a8d1cef35bf;hpb=038b5d9eab3e5dc2a203f064f22caa854f5b68ae;p=pyderasn.git diff --git a/pyderasn.py b/pyderasn.py index 6e772e0..5e2dd2b 100755 --- a/pyderasn.py +++ b/pyderasn.py @@ -1,6 +1,6 @@ #!/usr/bin/env python # coding: utf-8 -# PyDERASN -- Python ASN.1 DER codec with abstract structures +# PyDERASN -- Python ASN.1 DER/BER codec with abstract structures # Copyright (C) 2017-2018 Sergey Matveev # # This program is free software: you can redistribute it and/or modify @@ -16,10 +16,10 @@ # You should have received a copy of the GNU Lesser General Public # License along with this program. If not, see # . -"""Python ASN.1 DER codec with abstract structures +"""Python ASN.1 DER/BER codec with abstract structures -This library allows you to marshal and unmarshal various structures in -ASN.1 DER format, like this: +This library allows you to marshal various structures in ASN.1 DER +format, unmarshal them in BER/CER/DER ones. >>> i = Integer(123) >>> raw = i.encode() @@ -189,11 +189,18 @@ 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``). -When error occurs, then :py:exc:`pyderasn.DecodeError` is raised. +* ``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. .. _ctx: @@ -206,6 +213,7 @@ decoding process. Currently available context options: +* :ref:`bered ` * :ref:`defines_by_path ` * :ref:`strict_default_existence ` @@ -363,6 +371,29 @@ 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. +.. _bered_ctx: + +BER encoding +------------ + +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 +constructed primitive types should be parsed successfully. + +* If object is encoded in BER form (not the DER one), then ``bered`` + attribute is set to True. Only ``BOOLEAN``, ``BIT STRING``, ``OCTET + STRING`` can contain it. +* If object has an indefinite length encoding, then its ``lenindef`` + attribute is set to True. Only ``BIT STRING``, ``OCTET STRING``, + ``SEQUENCE``, ``SET``, ``SEQUENCE OF``, ``SET OF``, ``ANY`` can + contain it. +* If object has an indefinite length encoded explicit tag, then + ``expl_lenindef`` is set to True. + +EOC (end-of-contents) token's length is taken in advance in object's +value length. + Primitive types --------------- @@ -404,6 +435,10 @@ CommonString ____________ .. autoclass:: pyderasn.CommonString +NumericString +_____________ +.. autoclass:: pyderasn.NumericString + UTCTime _______ .. autoclass:: pyderasn.UTCTime @@ -464,6 +499,17 @@ Various .. autofunction:: pyderasn.tag_ctxp .. autofunction:: pyderasn.tag_ctxc .. autoclass:: pyderasn.Obj +.. autoclass:: pyderasn.DecodeError + :members: __init__ +.. autoclass:: pyderasn.NotEnoughData +.. autoclass:: pyderasn.LenIndefForm +.. autoclass:: pyderasn.TagMismatch +.. autoclass:: pyderasn.InvalidLength +.. autoclass:: pyderasn.InvalidOID +.. autoclass:: pyderasn.ObjUnknown +.. autoclass:: pyderasn.ObjNotReady +.. autoclass:: pyderasn.InvalidValueType +.. autoclass:: pyderasn.BoundsError """ from codecs import getdecoder @@ -516,6 +562,7 @@ __all__ = ( "InvalidOID", "InvalidValueType", "ISO646String", + "LenIndefForm", "NotEnoughData", "Null", "NumericString", @@ -563,6 +610,8 @@ TagClassReprs = { } EOC = b"\x00\x00" EOC_LEN = len(EOC) +LENINDEF = b"\x80" # length indefinite mark +LENINDEF_PP_CHAR = "I" if PY2 else "∞" ######################################################################## @@ -608,7 +657,7 @@ class NotEnoughData(DecodeError): pass -class LenIndefiniteForm(DecodeError): +class LenIndefForm(DecodeError): pass @@ -803,6 +852,11 @@ def len_encode(l): def len_decode(data): + """Decode length + + :returns: (decoded length, length's length, remaining data) + :raises LenIndefForm: if indefinite form encoding is met + """ if len(data) == 0: raise NotEnoughData("no data at all") first_octet = byte2int(data) @@ -812,7 +866,7 @@ def len_decode(data): if octets_num + 1 > len(data): raise NotEnoughData("encoded length is longer than data") if octets_num == 0: - raise LenIndefiniteForm() + raise LenIndefForm() if byte2int(data[1:]) == 0: raise DecodeError("leading zeros") l = 0 @@ -849,6 +903,8 @@ class Obj(object): "offset", "llen", "vlen", + "expl_lenindef", + "lenindef", "bered", ) @@ -863,14 +919,14 @@ 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 self.offset, self.llen, self.vlen = _decoded self.default = None + self.expl_lenindef = False + self.lenindef = False self.bered = False @property @@ -982,6 +1038,36 @@ class Obj(object): ) try: l, llen, v = len_decode(lv) + except LenIndefForm as err: + if not ctx.get("bered", False): + raise err.__class__( + msg=err.msg, + klass=self.__class__, + decode_path=decode_path, + offset=offset, + ) + llen, v = 1, lv[1:] + offset += tlen + llen + result = self._decode( + v, + offset=offset, + decode_path=decode_path, + ctx=ctx, + tag_only=tag_only, + ) + if tag_only: + return + obj, tail = result + eoc_expected, tail = tail[:EOC_LEN], tail[EOC_LEN:] + if eoc_expected.tobytes() != EOC: + raise DecodeError( + "no EOC", + klass=self.__class__, + decode_path=decode_path, + offset=offset, + ) + obj.vlen += EOC_LEN + obj.expl_lenindef = True except DecodeError as err: raise err.__class__( msg=err.msg, @@ -989,23 +1075,24 @@ class Obj(object): decode_path=decode_path, offset=offset, ) - if l > len(v): - raise NotEnoughData( - "encoded length is longer than data", - klass=self.__class__, + else: + if l > len(v): + raise NotEnoughData( + "encoded length is longer than data", + klass=self.__class__, + decode_path=decode_path, + offset=offset, + ) + result = self._decode( + v, + offset=offset + tlen + llen, decode_path=decode_path, - offset=offset, + ctx=ctx, + tag_only=tag_only, ) - result = self._decode( - v, - offset=offset + tlen + llen, - decode_path=decode_path, - ctx=ctx, - tag_only=tag_only, - ) - if tag_only: - return - obj, tail = result + if tag_only: + return + obj, tail = result return obj, (tail if leavemm else tail.tobytes()) @property @@ -1022,6 +1109,8 @@ class Obj(object): @property def expl_llen(self): + if self.expl_lenindef: + return 1 return len(len_encode(self.tlvlen)) @property @@ -1036,6 +1125,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 @@ -1082,6 +1206,9 @@ PP = namedtuple("PP", ( "expl_tlen", "expl_llen", "expl_vlen", + "expl_lenindef", + "lenindef", + "bered", )) @@ -1103,6 +1230,9 @@ def _pp( expl_tlen=None, expl_llen=None, expl_vlen=None, + expl_lenindef=False, + lenindef=False, + bered=False, ): return PP( asn1_type_name, @@ -1122,6 +1252,9 @@ def _pp( expl_tlen, expl_llen, expl_vlen, + expl_lenindef, + lenindef, + bered, ) @@ -1138,16 +1271,23 @@ 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) - cols.append(_colorize(col, "green", 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))) ent = pp.decode_path[-1] @@ -1175,6 +1315,8 @@ def pp_console_row( cols.append(_colorize(col, "blue", with_colours)) if pp.asn1_type_name.replace(" ", "") != pp.obj_name.upper(): cols.append(_colorize(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)) if pp.value is not None: value = pp.value @@ -1198,7 +1340,7 @@ def pp_console_row( def pp_console_blob(pp): - cols = [" " * len("XXXXXYY [X,X,XXXX]")] + 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): @@ -1453,7 +1595,11 @@ class Boolean(Obj): expl_tlen=self.expl_tlen if self.expled else None, expl_llen=self.expl_llen if self.expled else None, expl_vlen=self.expl_vlen if self.expled else None, + expl_lenindef=self.expl_lenindef, + bered=self.bered, ) + for pp in self.pps_lenindef(decode_path): + yield pp class Integer(Obj): @@ -1775,7 +1921,10 @@ class Integer(Obj): expl_tlen=self.expl_tlen if self.expled else None, expl_llen=self.expl_llen if self.expled else None, 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): @@ -1790,6 +1939,8 @@ class BitString(Obj): >>> b.bit_len 88 + >>> BitString("'0A3B5F291CD'H") + BIT STRING 44 bits 0a3b5f291cd0 >>> b = BitString("'010110000000'B") BIT STRING 12 bits 5800 >>> b.bit_len @@ -1816,6 +1967,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) @@ -1878,21 +2037,23 @@ class BitString(Obj): if isinstance(value, (string_types, binary_type)): if ( isinstance(value, string_types) and - value.startswith("'") and - value.endswith("'B") + value.startswith("'") ): - value = value[1:-2] - if not set(value) <= set(("0", "1")): - raise ValueError("B's coding contains unacceptable chars") - return self._bits2octets(value) - elif isinstance(value, binary_type): + if value.endswith("'B"): + value = value[1:-2] + if not set(value) <= set(("0", "1")): + raise ValueError("B's coding contains unacceptable chars") + return self._bits2octets(value) + elif value.endswith("'H"): + value = value[1:-2] + return ( + len(value) * 4, + hexdec(value + ("" if len(value) % 2 == 0 else "0")), + ) + if isinstance(value, binary_type): return (len(value) * 8, value) else: - raise InvalidValueType(( - self.__class__, - string_types, - binary_type, - )) + raise InvalidValueType((self.__class__, string_types, binary_type)) if isinstance(value, tuple): if ( len(value) == 2 and @@ -2081,18 +2242,19 @@ 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, ) if tag_only: return - eoc_expected = False + lenindef = False try: l, llen, v = len_decode(lv) - except LenIndefiniteForm: + except LenIndefForm: llen, l, v = 1, 0, lv[1:] - eoc_expected = True + lenindef = True except DecodeError as err: raise err.__class__( msg=err.msg, @@ -2100,14 +2262,14 @@ 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__, decode_path=decode_path, offset=offset, ) - if not eoc_expected and l == 0: + if not lenindef and l == 0: raise NotEnoughData( "zero length", klass=self.__class__, @@ -2118,7 +2280,7 @@ class BitString(Obj): sub_offset = offset + tlen + llen vlen = 0 while True: - if eoc_expected: + if lenindef: if v[:EOC_LEN].tobytes() == EOC: break else: @@ -2126,8 +2288,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)),) @@ -2141,7 +2304,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, ) @@ -2151,7 +2315,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, ) @@ -2160,7 +2325,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, ) @@ -2176,10 +2342,11 @@ class BitString(Obj): default=self.default, optional=self.optional, _specs=self.specs, - _decoded=(offset, llen, vlen + (EOC_LEN if eoc_expected else 0)), + _decoded=(offset, llen, vlen + (EOC_LEN if lenindef else 0)), ) + obj.lenindef = lenindef obj.bered = True - return obj, v[EOC_LEN if eoc_expected else 0:] + return obj, (v[EOC_LEN:] if lenindef else v) raise TagMismatch( klass=self.__class__, decode_path=decode_path, @@ -2215,12 +2382,17 @@ class BitString(Obj): expl_tlen=self.expl_tlen if self.expled else None, expl_llen=self.expl_llen if self.expled else None, expl_vlen=self.expl_vlen if self.expled else None, + expl_lenindef=self.expl_lenindef, + lenindef=self.lenindef, + bered=self.bered, ) defined_by, defined = self.defined or (None, None) if defined_by is not None: yield defined.pps( decode_path=decode_path + (DecodePathDefBy(defined_by),) ) + for pp in self.pps_lenindef(decode_path): + yield pp class OctetString(Obj): @@ -2238,6 +2410,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) @@ -2432,18 +2612,19 @@ 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, ) if tag_only: return - eoc_expected = False + lenindef = False try: l, llen, v = len_decode(lv) - except LenIndefiniteForm: + except LenIndefForm: llen, l, v = 1, 0, lv[1:] - eoc_expected = True + lenindef = True except DecodeError as err: raise err.__class__( msg=err.msg, @@ -2451,25 +2632,18 @@ 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 eoc_expected and l == 0: - raise NotEnoughData( - "zero length", - klass=self.__class__, - decode_path=decode_path, - offset=offset, - ) chunks = [] sub_offset = offset + tlen + llen vlen = 0 while True: - if eoc_expected: + if lenindef: if v[:EOC_LEN].tobytes() == EOC: break else: @@ -2477,8 +2651,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)),) @@ -2492,7 +2667,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, ) @@ -2500,12 +2676,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), @@ -2514,7 +2684,7 @@ class OctetString(Obj): expl=self._expl, default=self.default, optional=self.optional, - _decoded=(offset, llen, vlen + (EOC_LEN if eoc_expected else 0)), + _decoded=(offset, llen, vlen + (EOC_LEN if lenindef else 0)), ) except DecodeError as err: raise DecodeError( @@ -2530,8 +2700,9 @@ class OctetString(Obj): decode_path=decode_path, offset=offset, ) + obj.lenindef = lenindef obj.bered = True - return obj, v[EOC_LEN if eoc_expected else 0:] + return obj, (v[EOC_LEN:] if lenindef else v) raise TagMismatch( klass=self.__class__, decode_path=decode_path, @@ -2560,12 +2731,17 @@ class OctetString(Obj): expl_tlen=self.expl_tlen if self.expled else None, expl_llen=self.expl_llen if self.expled else None, expl_vlen=self.expl_vlen if self.expled else None, + expl_lenindef=self.expl_lenindef, + lenindef=self.lenindef, + bered=self.bered, ) defined_by, defined = self.defined or (None, None) if defined_by is not None: yield defined.pps( decode_path=decode_path + (DecodePathDefBy(defined_by),) ) + for pp in self.pps_lenindef(decode_path): + yield pp class Null(Obj): @@ -2696,7 +2872,10 @@ class Null(Obj): expl_tlen=self.expl_tlen if self.expled else None, expl_llen=self.expl_llen if self.expled else None, 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): @@ -2984,7 +3163,10 @@ class ObjectIdentifier(Obj): expl_tlen=self.expl_tlen if self.expled else None, expl_llen=self.expl_llen if self.expled else None, 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): @@ -3202,7 +3384,14 @@ class CommonString(OctetString): tlen=self.tlen, llen=self.llen, vlen=self.vlen, + expl_offset=self.expl_offset if self.expled else None, + expl_tlen=self.expl_tlen if self.expled else None, + expl_llen=self.expl_llen if self.expled else None, + 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): @@ -3213,6 +3402,10 @@ class UTF8String(CommonString): class NumericString(CommonString): + """Numeric string + + Its value is properly sanitized: only ASCII digits can be stored. + """ __slots__ = () tag_default = tag_encode(18) encoding = "ascii" @@ -3390,7 +3583,14 @@ class UTCTime(CommonString): tlen=self.tlen, llen=self.llen, vlen=self.vlen, + expl_offset=self.expl_offset if self.expled else None, + expl_tlen=self.expl_tlen if self.expled else None, + expl_llen=self.expl_llen if self.expled else None, + 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): @@ -3696,7 +3896,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 @@ -3721,9 +3921,12 @@ class Choice(Obj): tlen=self.tlen, llen=self.llen, vlen=self.vlen, + expl_lenindef=self.expl_lenindef, ) 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): @@ -3852,7 +4055,47 @@ class Any(Obj): def _decode(self, tlv, offset, decode_path, ctx, tag_only): try: t, tlen, lv = tag_strip(tlv) + except DecodeError as err: + raise err.__class__( + msg=err.msg, + klass=self.__class__, + decode_path=decode_path, + offset=offset, + ) + try: l, llen, v = len_decode(lv) + except LenIndefForm as err: + if not ctx.get("bered", False): + raise err.__class__( + msg=err.msg, + klass=self.__class__, + decode_path=decode_path, + offset=offset, + ) + llen, vlen, v = 1, 0, lv[1:] + sub_offset = offset + tlen + llen + chunk_i = 0 + 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, @@ -3899,12 +4142,16 @@ class Any(Obj): expl_tlen=self.expl_tlen if self.expled else None, expl_llen=self.expl_llen if self.expled else None, expl_vlen=self.expl_vlen if self.expled else None, + expl_lenindef=self.expl_lenindef, + lenindef=self.lenindef, ) defined_by, defined = self.defined or (None, None) if defined_by is not None: yield defined.pps( decode_path=decode_path + (DecodePathDefBy(defined_by),) ) + for pp in self.pps_lenindef(decode_path): + yield pp ######################################################################## @@ -4008,7 +4255,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: @@ -4199,8 +4446,19 @@ class Sequence(Obj): ) if tag_only: return + lenindef = False try: l, llen, v = len_decode(lv) + except LenIndefForm as err: + if not ctx.get("bered", False): + raise err.__class__( + msg=err.msg, + klass=self.__class__, + decode_path=decode_path, + offset=offset, + ) + l, llen, v = 0, 1, lv[1:] + lenindef = True except DecodeError as err: raise err.__class__( msg=err.msg, @@ -4215,11 +4473,16 @@ class Sequence(Obj): decode_path=decode_path, offset=offset, ) - v, tail = v[:l], v[l:] + if not lenindef: + v, tail = v[:l], v[l:] + vlen = 0 sub_offset = offset + tlen + llen values = {} for name, spec in self.specs.items(): - if len(v) == 0 and spec.optional: + if spec.optional and ( + (lenindef and v[:EOC_LEN].tobytes() == EOC) or + len(v) == 0 + ): continue sub_decode_path = decode_path + (name,) try: @@ -4282,7 +4545,9 @@ class Sequence(Obj): ) value.defined = (defined_by, defined_value) - sub_offset += (value.expl_tlvlen if value.expled else value.tlvlen) + value_len = value.fulllen + vlen += value_len + sub_offset += value_len v = v_tail if spec.default is not None and value == spec.default: if ctx.get("strict_default_existence", False): @@ -4309,7 +4574,17 @@ class Sequence(Obj): abs_decode_path(sub_decode_path[:-1], rel_path), (value, defined), )) - if len(v) > 0: + 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:] + vlen += EOC_LEN + elif len(v) > 0: raise DecodeError( "remaining data", klass=self.__class__, @@ -4322,9 +4597,10 @@ class Sequence(Obj): expl=self._expl, default=self.default, optional=self.optional, - _decoded=(offset, llen, l), + _decoded=(offset, llen, vlen), ) obj._value = values + obj.lenindef = lenindef return obj, tail def __repr__(self): @@ -4334,8 +4610,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( @@ -4354,12 +4630,16 @@ class Sequence(Obj): expl_tlen=self.expl_tlen if self.expled else None, expl_llen=self.expl_llen if self.expled else None, expl_vlen=self.expl_vlen if self.expled else None, + expl_lenindef=self.expl_lenindef, + lenindef=self.lenindef, ) for name in self.specs: value = self._value.get(name) 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): @@ -4395,8 +4675,19 @@ class Set(Sequence): ) if tag_only: return + lenindef = False try: l, llen, v = len_decode(lv) + except LenIndefForm as err: + if not ctx.get("bered", False): + raise err.__class__( + msg=err.msg, + klass=self.__class__, + decode_path=decode_path, + offset=offset, + ) + l, llen, v = 0, 1, lv[1:] + lenindef = True except DecodeError as err: raise err.__class__( msg=err.msg, @@ -4410,11 +4701,15 @@ class Set(Sequence): klass=self.__class__, offset=offset, ) - v, tail = v[:l], v[l:] + if not lenindef: + v, tail = v[:l], v[l:] + vlen = 0 sub_offset = offset + tlen + llen values = {} specs_items = self.specs.items while len(v) > 0: + if lenindef and v[:EOC_LEN].tobytes() == EOC: + break for name, spec in specs_items(): sub_decode_path = decode_path + (name,) try: @@ -4442,9 +4737,9 @@ class Set(Sequence): decode_path=sub_decode_path, ctx=ctx, ) - sub_offset += ( - 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 @@ -4455,9 +4750,26 @@ class Set(Sequence): expl=self._expl, default=self.default, optional=self.optional, - _decoded=(offset, llen, l), + _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( + "not all values are ready", + klass=self.__class__, + decode_path=decode_path, + offset=offset, + ) return obj, tail @@ -4659,8 +4971,19 @@ class SequenceOf(Obj): ) if tag_only: return + lenindef = False try: l, llen, v = len_decode(lv) + except LenIndefForm as err: + if not ctx.get("bered", False): + raise err.__class__( + msg=err.msg, + klass=self.__class__, + decode_path=decode_path, + offset=offset, + ) + l, llen, v = 0, 1, lv[1:] + lenindef = True except DecodeError as err: raise err.__class__( msg=err.msg, @@ -4675,11 +4998,15 @@ class SequenceOf(Obj): decode_path=decode_path, offset=offset, ) - v, tail = v[:l], v[l:] + if not lenindef: + v, tail = v[:l], v[l:] + vlen = 0 sub_offset = offset + tlen + llen _value = [] spec = self.spec while len(v) > 0: + if lenindef and v[:EOC_LEN].tobytes() == EOC: + break value, v_tail = spec.decode( v, sub_offset, @@ -4687,7 +5014,9 @@ class SequenceOf(Obj): decode_path=decode_path + (str(len(_value)),), ctx=ctx, ) - sub_offset += (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__( @@ -4698,8 +5027,18 @@ class SequenceOf(Obj): expl=self._expl, default=self.default, optional=self.optional, - _decoded=(offset, llen, l), + _decoded=(offset, llen, vlen + (EOC_LEN if lenindef else 0)), ) + 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): @@ -4725,9 +5064,13 @@ class SequenceOf(Obj): expl_tlen=self.expl_tlen if self.expled else None, expl_llen=self.expl_llen if self.expled else None, expl_vlen=self.expl_vlen if self.expled else None, + expl_lenindef=self.expl_lenindef, + lenindef=self.lenindef, ) 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): @@ -4808,7 +5151,7 @@ def generic_decoder(): # pragma: no cover def main(): # pragma: no cover import argparse - parser = argparse.ArgumentParser(description="PyDERASN ASN.1 DER decoder") + parser = argparse.ArgumentParser(description="PyDERASN ASN.1 BER/DER decoder") parser.add_argument( "--skip", type=int, @@ -4827,6 +5170,11 @@ def main(): # pragma: no cover "--defines-by-path", help="Python path to decoder's defines_by_path", ) + parser.add_argument( + "--nobered", + action='store_true', + help="Disallow BER encoding", + ) parser.add_argument( "DERFile", type=argparse.FileType("rb"), @@ -4843,13 +5191,10 @@ def main(): # pragma: no cover pprinter = partial(pprint, big_blobs=True) else: schema, pprinter = generic_decoder() - obj, tail = schema().decode( - der, - ctx=( - None if args.defines_by_path is None else - {"defines_by_path": obj_by_path(args.defines_by_path)} - ), - ) + ctx = {"bered": not args.nobered} + 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) print(pprinter( obj, oids=oids,