X-Git-Url: http://www.git.cypherpunks.ru/?a=blobdiff_plain;f=pyderasn.py;h=188e2ac49e1ca06582d2fba6270515461f158940;hb=bf66903892de8a8c52ba44d18c97a1e7ea9a163a;hp=ed5f0e810e4d6211d055a2dd3eacd7bb4b814c3d;hpb=33f3d3d751503ff3bebac259a2657dd204962b89;p=pyderasn.git diff --git a/pyderasn.py b/pyderasn.py index ed5f0e8..188e2ac 100755 --- a/pyderasn.py +++ b/pyderasn.py @@ -369,10 +369,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 @@ -496,6 +492,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 @@ -548,6 +555,7 @@ __all__ = ( "InvalidOID", "InvalidValueType", "ISO646String", + "LenIndefForm", "NotEnoughData", "Null", "NumericString", @@ -595,6 +603,8 @@ TagClassReprs = { } EOC = b"\x00\x00" EOC_LEN = len(EOC) +LENINDEF = b"\x80" # length indefinite mark +LENINDEF_PP_CHAR = "I" if PY2 else "∞" ######################################################################## @@ -902,9 +912,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 @@ -1046,7 +1054,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, ) @@ -1109,6 +1118,33 @@ class Obj(object): def expl_tlvlen(self): return self.expl_tlen + self.expl_llen + self.expl_vlen + 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 @@ -1220,25 +1256,22 @@ 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) - 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) + 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))) @@ -1292,7 +1325,7 @@ def pp_console_row( def pp_console_blob(pp): - cols = [" " * len("XXXXXYY [X,X,XXXX]YY")] + 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): @@ -1550,6 +1583,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): @@ -1873,6 +1908,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): @@ -1915,6 +1952,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) @@ -1990,9 +2035,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)) @@ -2184,7 +2227,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, ) @@ -2203,7 +2247,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__, @@ -2229,8 +2273,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)),) @@ -2244,7 +2289,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, ) @@ -2254,7 +2300,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, ) @@ -2263,7 +2310,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, ) @@ -2328,6 +2376,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): @@ -2345,6 +2395,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) @@ -2539,7 +2597,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, ) @@ -2558,20 +2617,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 @@ -2584,8 +2636,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)),) @@ -2599,7 +2652,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, ) @@ -2607,12 +2661,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), @@ -2677,6 +2725,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): @@ -2809,6 +2859,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): @@ -3098,6 +3150,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): @@ -3321,6 +3375,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): @@ -3518,6 +3574,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): @@ -3852,6 +3910,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): @@ -4000,29 +4060,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, @@ -4077,6 +4135,8 @@ class Any(Obj): yield defined.pps( decode_path=decode_path + (DecodePathDefBy(defined_by),) ) + for pp in self.pps_lenindef(decode_path): + yield pp ######################################################################## @@ -4180,7 +4240,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: @@ -4535,8 +4595,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( @@ -4563,6 +4623,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): @@ -4676,15 +4738,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): @@ -4941,10 +5012,19 @@ class SequenceOf(Obj): expl=self._expl, default=self.default, optional=self.optional, - _decoded=(offset, llen, vlen), + _decoded=(offset, llen, vlen + (EOC_LEN if lenindef else 0)), ) - obj.lenindef = lenindef - return obj, (v[EOC_LEN:] if lenindef else tail) + 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]" % ( @@ -4974,6 +5054,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):