#!/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 <stargrave@stargrave.org>
#
# This program is free software: you can redistribute it and/or modify
# You should have received a copy of the GNU Lesser General Public
# License along with this program. If not, see
# <http://www.gnu.org/licenses/>.
-"""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()
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:
Currently available context options:
+* :ref:`bered <bered_ctx>`
* :ref:`defines_by_path <defines_by_path_ctx>`
* :ref:`strict_default_existence <strict_default_existence_ctx>`
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 <ctx>` 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
---------------
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)
"offset",
"llen",
"vlen",
- "lenindef",
"expl_lenindef",
+ "lenindef",
"bered",
)
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
"expl_tlen",
"expl_llen",
"expl_vlen",
+ "expl_lenindef",
+ "lenindef",
+ "bered",
))
expl_tlen=None,
expl_llen=None,
expl_vlen=None,
+ expl_lenindef=False,
+ lenindef=False,
+ bered=False,
):
return PP(
asn1_type_name,
expl_tlen,
expl_llen,
expl_vlen,
+ expl_lenindef,
+ lenindef,
+ bered,
)
)
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]
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
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):
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,
)
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,
)
>>> b.bit_len
88
+ >>> BitString("'0A3B5F291CD'H")
+ BIT STRING 44 bits 0a3b5f291cd0
>>> b = BitString("'010110000000'B")
BIT STRING 12 bits 5800
>>> b.bit_len
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
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:
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:
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,
)
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,
)
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,
)
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,
)
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,))
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:
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)
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),))
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,
"--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"),
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)