X-Git-Url: http://www.git.cypherpunks.ru/?a=blobdiff_plain;f=pyderasn.py;h=f7e6c4c53f9cb5d5316f555ca42658b0301d4308;hb=2d45a224943c79e95cbd4913b44420788bc6c17d;hp=0986475e85766d203da55f6e09a6720bed12e66b;hpb=63fa134af8329d7b1b2f723618bdb48c83a21110;p=pyderasn.git diff --git a/pyderasn.py b/pyderasn.py index 0986475..f7e6c4c 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() @@ -193,7 +193,7 @@ explicit tag. If you want to know information about it, then use: 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. +When error occurs, :py:exc:`pyderasn.DecodeError` is raised. .. _ctx: @@ -206,6 +206,7 @@ decoding process. Currently available context options: +* :ref:`bered ` * :ref:`defines_by_path ` * :ref:`strict_default_existence ` @@ -363,6 +364,33 @@ 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 +------------ + +.. 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 +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 --------------- @@ -854,8 +882,8 @@ class Obj(object): "offset", "llen", "vlen", - "lenindef", "expl_lenindef", + "lenindef", "bered", ) @@ -878,8 +906,8 @@ class Obj(object): self.optional = optional self.offset, self.llen, self.vlen = _decoded self.default = None - self.lenindef = False self.expl_lenindef = False + self.lenindef = False self.bered = False @property @@ -1123,6 +1151,9 @@ PP = namedtuple("PP", ( "expl_tlen", "expl_llen", "expl_vlen", + "expl_lenindef", + "lenindef", + "bered", )) @@ -1144,6 +1175,9 @@ def _pp( expl_tlen=None, expl_llen=None, expl_vlen=None, + expl_lenindef=False, + lenindef=False, + bered=False, ): return PP( asn1_type_name, @@ -1163,6 +1197,9 @@ def _pp( expl_tlen, expl_llen, expl_vlen, + expl_lenindef, + lenindef, + bered, ) @@ -1188,7 +1225,17 @@ def pp_console_row( ) 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 = _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(col) if len(pp.decode_path) > 0: cols.append(" ." * (len(pp.decode_path))) ent = pp.decode_path[-1] @@ -1216,6 +1263,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 @@ -1239,7 +1288,7 @@ def pp_console_row( def pp_console_blob(pp): - cols = [" " * len("XXXXXYY [X,X,XXXX]")] + cols = [" " * len("XXXXXYY [X,X,XXXX]YY")] if len(pp.decode_path) > 0: cols.append(" ." * (len(pp.decode_path) + 1)) if isinstance(pp.blob, binary_type): @@ -1494,6 +1543,8 @@ 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, ) @@ -1816,6 +1867,7 @@ 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, ) @@ -1831,6 +1883,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 @@ -1919,21 +1973,25 @@ 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) + 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")), + ) + else: + raise InvalidValueType((self.__class__, string_types, binary_type)) elif 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 @@ -2257,6 +2315,9 @@ 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: @@ -2603,6 +2664,9 @@ 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: @@ -2739,6 +2803,7 @@ 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, ) @@ -3027,6 +3092,7 @@ 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, ) @@ -3249,6 +3315,7 @@ class CommonString(OctetString): 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, ) @@ -3441,6 +3508,7 @@ class UTCTime(CommonString): 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, ) @@ -3772,6 +3840,7 @@ 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,)) @@ -3992,6 +4061,8 @@ 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: @@ -4476,6 +4547,8 @@ 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) @@ -4888,6 +4961,8 @@ 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),)) @@ -4971,7 +5046,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, @@ -4990,6 +5065,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"), @@ -5006,7 +5086,7 @@ def main(): # pragma: no cover pprinter = partial(pprint, big_blobs=True) else: schema, pprinter = generic_decoder() - ctx = {"bered": True} + 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)